No description
  • JavaScript 89.7%
  • CSS 9.7%
  • Dockerfile 0.4%
  • HTML 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
jlequen f872afaf2c Ajouter un historique de lecture parcourable
Une page dédiée listant toutes les lectures du serveur, groupées par jour,
avec filtres spectateur / bibliothèque / type / période, recherche textuelle
et export CSV.

Les filtres que Plex sait appliquer (compte, bibliothèque, dates) lui sont
délégués, ce qui garde la pagination côté serveur. Le type et la recherche
n'existent qu'après enrichissement des métadonnées : ils imposent un balayage,
borné à 4000 lectures et annoncé dans la réponse.

Au passage, une correction : un filtre à opérateur s'écrit `viewedAt>` et non
`viewedAt>=`. Le `=` fait partie de la requête, pas de la clé — encodé en %3D,
Plex ignore le filtre en silence et répond 200 avec l'historique complet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:24:46 +02:00
server Ajouter un historique de lecture parcourable 2026-08-23 13:24:46 +02:00
web Ajouter un historique de lecture parcourable 2026-08-23 13:24:46 +02:00
.dockerignore PerPlex — interface web de gestion pour serveur Plex 2026-08-20 16:42:41 +02:00
.env.example Nom d'image configurable, pour permettre le pull depuis Portainer 2026-08-21 09:13:48 +02:00
.gitignore PerPlex — interface web de gestion pour serveur Plex 2026-08-20 16:42:41 +02:00
docker-compose.yml Ménage : jamais vus, mal appariés, fichiers disparus et orphelins 2026-08-21 12:09:18 +02:00
Dockerfile Rendre la version visible, et rattacher les partages au compte connecté 2026-08-21 11:44:26 +02:00
package-lock.json Historique par spectateur, maintenance, appariement, notifications 2026-08-21 12:44:56 +02:00
package.json PerPlex — interface web de gestion pour serveur Plex 2026-08-20 16:42:41 +02:00
README.md Historique par spectateur, maintenance, appariement, notifications 2026-08-21 12:44:56 +02:00

PerPlex

Interface web pour gérer ton serveur Plex. Première fonction : trouver les doublons et faire le ménage.

Pensée pour le téléphone d'abord — c'est là qu'on se rend compte que le NAS est plein.

Démarrage

Une seule commande, puis tout se passe dans le navigateur :

docker compose up -d

Ouvre http://localhost:3001 (ou http://<ip-de-la-machine>:3001 depuis ton téléphone), clique sur « Connecter mon serveur Plex », et suis l'assistant.

Comment marche la connexion

PerPlex n'a jamais besoin de ton mot de passe. L'app affiche un code à 4 caractères et tu l'autorises depuis un appareil déjà connecté à Plex — exactement comme quand tu relies une TV :

  1. Sur le même appareil : le bouton « Autoriser sur cet appareil » ouvre Plex, tu valides, c'est fini.
  2. Depuis un autre appareil : va sur plex.tv/link et saisis le code affiché.

PerPlex récupère alors la liste de tes serveurs, teste chaque adresse pour trouver celle qui répond depuis le conteneur, et tu choisis lequel gérer. Le token est stocké côté serveur dans le volume perplex-config (fichier en 0600) et n'est jamais envoyé au navigateur.

Pour changer de serveur plus tard : clique sur le nom du serveur en haut à droite → Changer de serveur.

Si tu as déjà ton adresse et ton token sous la main, le lien « J'ai déjà mon adresse et mon token » en bas de l'assistant permet de les saisir directement.

Ce que ça fait

  • Regroupe les fichiers qui correspondent au même film ou au même épisode, qu'ils soient rangés sous une seule fiche Plex (« versions multiples ») ou éclatés sur plusieurs fiches distinctes (« fiches séparées »).
  • Le rapprochement se fait sur l'identifiant externe (IMDb / TMDb / TVDb) quand Plex l'a, sinon sur titre + année normalisés — accents, casse et ponctuation ignorés.
  • Classe chaque version de la meilleure à la moins bonne. La meilleure est marquée à garder, les autres reçoivent une raison lisible.
  • Te laisse cocher ce qui part, puis supprime réellement les fichiers via Plex.

Ménage

Au-delà des doublons, l'onglet Ménage cherche ce qui occupe de l'espace sans servir, et ce qui est mal rangé :

  • Jamais regardés — aucune lecture depuis leur ajout, avec l'ancienneté et la place occupée. Les séries sont comptées comme un titre, pas comme 200 épisodes.
  • Pas revus depuis X mois — vus au moins une fois, mais plus depuis longtemps. Un titre vu sans date enregistrée n'est jamais proposé : on ne suggère pas de supprimer ce qu'on est incapable de dater.
  • Fiches mal appariées — sans identifiant externe, sans année, sans affiche, ou dont le titre ressemble encore à un nom de fichier. À corriger dans Plex plutôt qu'à supprimer.
  • Fichiers disparus — référencés par Plex mais absents du disque. C'est ce qui remplit la corbeille, que l'on peut vider depuis là. La vérification est explicite : elle force Plex à contrôler chaque média et prend plusieurs minutes.
  • Fichiers orphelins — présents sur le disque mais absents de la bibliothèque.

Ce dernier point demande que tes dossiers médias soient visibles depuis le conteneur, aux mêmes chemins que ceux déclarés dans Plex. Sans montage, PerPlex le dit franchement plutôt que de renvoyer une liste vide :

    volumes:
      - perplex-config:/config
      - /media/monNAS/Films:/media/monNAS/Films:ro

Le montage en lecture seule (:ro) est volontaire : PerPlex ne supprime jamais un fichier orphelin, rien ne garantit qu'il soit inutile.

Choisir ce qui compte

Le bouton « Quel fichier garder ? » ouvre les critères de classement. Ils sont appliqués de haut en bas : le premier qui départage deux fichiers décide, les suivants ne servent qu'en cas d'égalité. Chacun se réordonne et se désactive.

Critère Ce qu'il regarde
Résolution Le palier (4K, 1080p…), jamais la hauteur en pixels — deux fichiers 4K de formats d'image différents n'ont pas le même nombre de lignes et sont pourtant à égalité
Débit vidéo Le débit global, par paliers de tolérance
Débit par mégapixel La compression réelle. Placé avant la résolution, un 1080p généreux peut battre une 4K trop compressée
Qualité audio Nombre de canaux d'abord, puis le codec (TrueHD/DTS-HD > EAC3 > AC3)
Taille du fichier Le plus gros gagne
Codec vidéo moderne AV1 > HEVC > H.264, à débit égal
Résolution sauf si trop compressée La meilleure définition gagne, sauf quand son débit par mégapixel tombe sous le seuil réglable : elle perd alors tout avantage de définition, et ce sont le débit, le son ou la taille qui départagent
Ancienneté Garde le plus ancien : c'est lui qui porte l'historique de visionnage

La tolérance sur le débit (15 % par défaut) définit en dessous de quel écart deux débits sont jugés équivalents. C'est ce qui permet à l'audio de trancher entre deux encodages quasi identiques, au lieu de laisser 1 Mb/s d'écart décider seul.

Par défaut : résolution → débit → audio → taille → ancienneté.

Mettre des doublons de côté

Une version longue et une version cinéma ne sont pas des doublons. Le bouton « Mettre de côté » sur un titre l'exclut définitivement des résultats — il ne pourra plus être supprimé, même par erreur. La liste se gère depuis « Mis de côté », et tout est réintégrable.

Deux règles automatiques, désactivées par défaut, écartent d'office :

  • Cadrages différents — une ouverture IMAX (1.78:1) face à du scope (2.20:1) ne montre pas la même image.
  • Durées différentes — director's cut, version longue, montage cinéma.

Même sans ces règles, PerPlex affiche un avertissement ⚠ sur les titres concernés, ainsi que lorsque le fichier conservé n'a pas la meilleure piste audio, ou qu'une définition supérieure cache un encodage bien plus compressé.

Garde-fous

La suppression est définitive : Plex efface le fichier du disque, il n'y a pas de corbeille.

  • Un groupe ne peut jamais perdre tous ses fichiers. C'est vérifié côté serveur, pas seulement dans l'interface.
  • La sélection est recoupée avec le dernier scan : si l'état a changé, la requête est refusée.
  • Il faut taper SUPPRIMER dans la boîte de confirmation.
  • Si la dernière version d'une fiche est supprimée, PerPlex supprime la fiche entière plutôt que de laisser une entrée orpheline dans ta bibliothèque.
  • Suppressions envoyées une par une : Plex encaisse mal les requêtes concurrentes.
  • Rien n'est coché d'office : c'est toi qui déclenches la sélection.

Côté Plex, il faut avoir activé Réglages Bibliothèque « Autoriser la suppression de médias ». Sans ça, PerPlex refuse l'opération avec un message explicite. L'état de ce réglage est visible dans la fiche du serveur.

Si aucun serveur n'est joignable

L'assistant liste tes serveurs mais les marque « injoignable » ? Le conteneur n'est pas sur le même réseau que Plex.

Le plus souvent, l'assistant trouve tout seul l'adresse LAN (192.168.x.x) et ça marche. Sinon, décommente network_mode: host dans docker-compose.yml, retire le bloc ports, et relance docker compose up -d.

Maintenance et notifications

L'onglet Maintenance expose les tâches du majordome de Plex — sauvegarde et optimisation de la base, nettoyage des paquets obsolètes, analyse des bibliothèques — et permet de déclencher celles qui ont du sens hors de la fenêtre de maintenance. Les activités en cours s'y suivent en direct.

Les notifications arrivent même quand PerPlex est fermé : c'est tout leur intérêt. Chaque navigateur s'inscrit séparément, et une surveillance de fond alerte quand un transcodage n'arrive plus à suivre la lecture, quand un serveur ne répond plus, et quand il revient. Sur iPhone elles exigent que PerPlex soit d'abord ajouté à l'écran d'accueil.

Les clés VAPID sont générées au premier démarrage et conservées : les régénérer invaliderait toutes les inscriptions existantes.

Installer comme application

PerPlex est une PWA : sur téléphone comme sur bureau, le navigateur propose de l'installer, et elle s'ouvre alors en plein écran avec sa propre icône. Un bandeau discret le propose au bon moment ; sinon, le menu du navigateur contient « Installer PerPlex » (ou « Ajouter à l'écran d'accueil » sur iOS).

Un service worker garde la coquille de l'application en cache : elle s'ouvre même hors ligne. Les réponses de l'API ne sont jamais mises en cache — un tableau de bord affichant les chiffres d'hier sans le dire serait pire qu'un écran d'erreur, et ces réponses dépendent de la session.

Quand une nouvelle version est déployée, un bandeau propose de recharger plutôt que de laisser une application installée sur une version périmée.

Sécurité

PerPlex peut supprimer des fichiers et couper des lectures. Au premier lancement, l'interface propose de la protéger — deux méthodes :

Compte Plex (recommandé). Tu autorises depuis un appareil déjà connecté à Plex, comme pour la liaison du serveur. L'autorisation est déléguée à Plex : est admis qui a accès au serveur connecté. Par défaut, seul le propriétaire entre ; un réglage ouvre l'accès aux comptes avec qui le serveur est partagé.

Mot de passe unique, haché en scrypt dans la base, huit caractères minimum. Aucune récupération possible. Il peut aussi être imposé par la variable PERPLEX_PASSWORD : l'interface ne permet alors plus de le modifier.

Dans les deux cas la session tient dans un cookie HttpOnly SameSite=Lax valable 30 jours, révocable, et huit tentatives ratées bloquent l'adresse cinq minutes. Toute l'API est verrouillée, sauf les routes d'authentification elles-mêmes.

Tant qu'aucune méthode n'est choisie, PerPlex reste ouvert : verrouiller une instance existante enfermerait son propriétaire dehors. La fiche du serveur le signale.

Même protégé, garde-le de préférence sur ton réseau local ou derrière un VPN.

Configuration

Tout est facultatif — copie .env.example en .env seulement si tu veux changer quelque chose.

Variable Défaut Rôle
PERPLEX_PORT 3001 Port exposé sur la machine hôte
SCAN_CACHE_TTL 300 Durée de vie du cache de scan, en secondes
PLEX_URL Impose un serveur sans passer par l'assistant
PLEX_TOKEN Idem. Si les deux sont définis, l'interface ne permet plus de changer de serveur
PLEX_INSECURE 0 1 pour accepter un certificat TLS auto-signé
PERPLEX_PASSWORD Impose un mot de passe ; l'interface ne peut plus le changer
PERPLEX_CORS_ORIGIN Autorise une origine externe. Inutile en usage normal

La connexion, les critères de classement et les doublons mis de côté vivent dans une base SQLite (/config/perplex.db) portée par le volume perplex-config. Tout survit aux redémarrages et aux mises à jour de l'image. Une ancienne configuration config.json est reprise automatiquement au premier démarrage.

Publier l'image (Portainer, autre machine)

Par défaut l'image porte un nom local : elle se construit sur place et ne se télécharge de nulle part. C'est pour ça que le bouton Pull and redeploy de Portainer échoue avec pull access denied for perplex — il n'y a rien à tirer.

Deux options.

Laisser Portainer construire. Dans la stack, décoche Re-pull image avant Update the stack. Avec une stack de type Repository, pointe-la sur ce dépôt et coche Re-clone plutôt que Re-pull.

Publier dans un registre, ce qui rend le pull possible partout. Avec le registre de ta forge Forgejo :

# jeton : Paramètres  Applications  Générer un jeton (portée : write:package)
echo "$TOKEN" | docker login forge.lequen.fr -u jlequen --password-stdin

export PERPLEX_IMAGE=forge.lequen.fr/jlequen/perplex:latest
docker compose build
docker compose push

Sur la machine qui héberge Portainer, mets la même valeur dans .env, ajoute PERPLEX_PULL_POLICY=always, et Pull and redeploy fonctionnera.

Structure

server/          API Express — parle à Plex, détecte les doublons, supprime
  src/plextv.js       liaison par code et découverte des serveurs (plex.tv)
  src/setup.js        routes de l'assistant de connexion
  src/db.js           base SQLite : réglages et exceptions
  src/store.js        accès à la configuration de connexion
  src/preferences.js  critères de classement et comparateur
  src/plex.js         client HTTP Plex (pagination, erreurs, suppression)
  src/duplicates.js   regroupement et classement des versions
  src/index.js        routes HTTP + garde-fous de suppression
web/             Interface React (Vite), mobile-first

Développement

Uniquement si tu as Node 20+ en local — ce n'est pas nécessaire pour utiliser l'app.

npm install
npm run dev      # interface sur :5173, API sur :3001

La base atterrit alors dans data/perplex.db au lieu du volume Docker.

Limites connues

  • Le scan porte sur les bibliothèques Films et Séries. La musique et les photos sont ignorées.
  • Deux versions volontaires d'un même film apparaissent comme des doublons. Les règles d'écartement automatique et la liste des exceptions sont là pour ça, mais la décision reste la tienne.
  • Aucun classement automatique ne remplace un coup d'œil : PerPlex compare des chiffres (résolution, débit, canaux), pas des images. Les avertissements ⚠ signalent les cas où ces chiffres ne suffisent pas.
  • Une bibliothèque de plusieurs milliers de titres met quelques dizaines de secondes à charger la première fois. Le résultat est ensuite mis en cache pendant SCAN_CACHE_TTL.