Photobooth Desktop : une cabine photo de la webcam à l'impression
Après la version web de ma cabine photo, j’ai voulu une déclinaison desktop : une app Electron installable sur un poste d’événement, pilotable à la souris mais aussi au clavier et à la manette, avec impression directe sur une photo 4×6. Voici comment est né Photobooth Desktop.
Le principe reste le même : capture webcam, superposition de fonds PNG, enregistrement ou impression du résultat. La version desktop ajoute le plein écran kiosque, les raccourcis clavier, le support manette et l’impression.
Stack technique
| Couche | Technologie |
|---|---|
| Runtime | Electron 43 |
| UI | Preact 10 (+ TypeScript) |
| Bundle | esbuild |
| Packaging | electron-builder (deb, rpm, AppImage, NSIS) |
| Capture | getUserMedia + Canvas 2D |
| i18n | JSON (en, fr, de, es) |
Une stack volontairement minimale : pas de framework Electron lourd, pas de bundler configurable à outrance. Preact + esbuild tiennent le rendu, le reste est du Node natif.
Architecture
L’application suit l’architecture Electron classique à trois processus :
src/main/index.ts → processus principal (IPC, fs, impression) |
Tout passe par un pont IPC 1:1 avec le renderer : chaque ipcMain.handle correspond à une fonction de lib/rpc.ts. La sécurité est standard (contextIsolation: true, nodeIntegration: false, navigation externe redirigée vers le navigateur système) :
function register<Params, Response>(channel: string, handler: (params: Params) => Response | Promise<Response>) { |
Gestion des données : le bundle n’est pas le data store
Point important pour une app distribuée en AppImage/deb : le bundle est en lecture seule (ou écrasé à chaque rebuild). Config et fonds vivent donc dans le cache dir OS, photos dans le dossier Images :
function dataDir(): string { |
Au premier lancement, les défauts sont seedés depuis le bundle : les PNG de backgrounds/ sont copiés vers le cache, le config.json par défaut également. L’utilisateur peut ensuite importer ses propres fonds depuis les réglages (chaque import est sanitisé, nom de fichier assaini, et dédupliqué).
Capture et composition sur Canvas
Le cœur du rendu, c’est du Canvas 2D pur. Trois fonctions font tout :
Le recadrage cover-fit de la vidéo
La webcam a un ratio quelconque ; la photo finale est fixe (1800×1200). On centre-crop la vidéo pour couvrir le cadre :
export function drawVideoCrop(c: CanvasRenderingContext2D, video: HTMLVideoElement) { |
Le fond PNG positionnable
Un fond de cabine photo est rarement exactement au ratio de la photo. Plutôt que de l’étirer, on le cover-fit puis on l’ancre selon une position configurable (top, left bottom, 40%…) :
export function drawBgTo(c, img, cw, ch, position) { |
parsePosition traduit la chaîne (top, left bottom, 40%…) en coordonnées d’ancrage normalisées 0..1. Utile pour un décor haut dont on veut montrer le sommet.
Le mode photo strip
Quatre captures sont assemblées en grille 2×2, chaque shot réduit de moitié avec un gap de 2 px :
export function createStrip(frames: ImageData[]): string { |
Enregistrement et impression
La photo est encodée en data URL PNG côté renderer puis envoyée au processus principal. Celui-ci l’écrit dans ~/Pictures/photobooth et, si demandé, la confie à lp (l’imprimante CUPS) avec les options 4×6 glossy :
register("savePhoto", async ({ image, print }: { image: string; print: boolean }) => { |
Simple et efficace : pas de lib d’impression, lp fait le travail sur Linux. Sur Windows, le même chemin d’enregistrement fonctionne, l’impression passe par le dialogue système.
Mode kiosque
Pour un usage événementiel, l’app gère ses permissions webcam sans prompt (impossible dans une fenêtre kiosque) :
session.defaultSession.setPermissionRequestHandler((_wc, permission, callback) => { |
Et un splash screen (400×280, sans cadre, toujours au premier plan) affiche la progression de chargement pendant que le renderer précharge les fonds, avant de laisser place à la fenêtre principale 1400×900.
Fonctionnalités
- Flux webcam en direct avec superpositions PNG en temps réel
- Positionnement des fonds : top, bottom, left, right, center, pourcentages
- Capture 1800×1200 paysage (10×15 cm à 300 DPI)
- Photo strip : 4 shots en grille 2×2
- Compte à rebours personnalisable (3s, 5s, 10s)
- Mode miroir, sélecteur de caméra
- Raccourcis clavier configurables et support manette (boutons + axes)
- i18n : anglais, français, allemand, espagnol
- Watermark optionnel (barre semi-transparente en bas de photo)
- Réglages dans l’app (plus d’édition manuelle de config.json), export/import des paramètres et fonds en un seul JSON
Packaging
Le build produit des paquets natifs pour chaque plateforme :
yarn electron:dev # Build bundles + lance Electron (DevTools auto-open) |
L’installation se fait ensuite comme n’importe quelle app : dpkg -i, rpm -i ou AppImage. Product page : giwi.github.io/photobooth-desktop.
Le projet est open source (Apache-2.0) : github.com/Giwi/photobooth-desktop. Il fait partie de la famille Photobooth. Si vous montez une cabine pour un mariage ou un événement, cette version desktop est celle qui tourne le mieux sur un poste dédié.