Index
Le 15 nivôse an CCXXXI

Galerie de photos

Je fais un peu de photo et jusqu'ici j'utilisais SFPG, Single File PHP Gallery pour en faire des galeries. Ça marche bien et c'est assez pratique, mais je n'ai pas besoin d'admin, je n'utilise pas la plupart des fonctionnalités, et il doit prendre du temps pour générer toutes les images de prévisualisation au premier accès à une galerie.

J'ai donc décidé de faire plutôt des galeries pré-générées, et plutôt en PHP pour avoir quelque chose qui ne casse pas aléatoirement à chaque mise à jour de NPM, ruby, python ou je ne sais quoi d'autre... et je n'en ai pas vraiment trouvé. Après avoir testé en particulier Expose en bash qui marche pas trop mal mais qui n'est pas si simple à utiliser que j'aurais voulu, j'ai évidemment décidé d'en faire un moi-même.

Du coup j'en ai fait un, un truc relativement simple qui lit un dossier et crée une galerie, récursivement ou pas, avec un peu de configuration au niveau de la taille des vignettes, des images plein écran, etc.

Le projet est astucieusement nommé "php-galerie" et est disponible sur Github : https://github.com/seeschloss/php-galerie. Il consiste en un fichier PHP uniquement, à utiliser en ligne de commande. Normalement, --help devrait être suffisant pour comprendre comment l'utiliser.

Un exemple de galerie de photos est visible ici : https://seos.fr/html/galerie/index.html.

Jusqu'ici je faisais mes photos principalement avec un Pentax K20D et les images générées contiennent une image "PreviewImage" (qu'on peut récupérer avec exiftool) assez petite, de seulement 640x480 pixels, donc pas très intéressante pour une galerie, mais récemment je suis entré un possession d'un nouvel appareil photo de marque Sony, et la PreviewImage cette fois-ci fait 1616x1080, ce qui est déjà plus intéressant puisque de chez mes parents et leur connexion ADSL qui plafonne à 100 ko/s, télécharger des images de 15 Mo en 6000x4000 pour les afficher sur un écran en 1680x1050 est plutôt lent pas très optimal.

Heureusement, PHP me fournit directement exif_read_data ! Super, me dis-je, je teste vite fait et je récupère sans problème la vignette, en 120x120, que j'utilise du coup pour gagner pas mal de temps en évitant de devoir les générer à partir de l'image grand format (sauf si on veut des vignettes avec une taille supérieure à celle des vignettes pré-générées bien sûr).

Il ne me reste plus qu'à récupérer la "PreviewImage" et là... c'est le drame. Les métadonnées JPEG sont stockées sous un format basé sur des segments de 64 kilooctets, ce qui est suffisant pour une petite vignette mais pas pour une vraie image de prévisualisation. Pour contourner ce problème, le format MPF, Multi-Picture Format est utilisé, en tout cas par Sony dans mon cas.

Multi-Picture Format

C'est un sacré bordel. En gros, ça se présente comme ça :

  1. Le format JPEG inclut un certain nombre de "marqueurs" de métadonnées pour stocker des informations diverses (cf. http://fileformats.archiveteam.org/wiki/JPEG)
  2. Le marqueur "APP2", s'il commence par le texte "MPF", contient des données au format TIFF
  3. Ces données sont organisées en un certain nombre de tags définis par le format MPF (cf. https://exiftool.org/TagNames/MPF.html)
  4. Un des tags définit la liste des images
  5. Pour chacune de ces images, on connait le type ("grande vignette", "panorama", "image principale", etc) et l'emplacement des données
  6. L'emplacement des données est donné sous format d'un offset relatif au début des données MPF et d'une longueur, sauf pour l'image principale dont l'offset est 0 et est relatif au début du fichier (mais on s'en fout, en fait).

Si exif_read_data ne peut pas m'aider ici, c'est parce que ce que je cherche n'est pas dans les données Exif (qui sont stockées sous le marqueur "APP1"). Heureusement, pour une raison qui avait probablement l'air très bonne à l'époque on peut récupérer les autres marqueurs en PHP grâce à la fonction... getimagesize qui retourne un tas de trucs qui n'ont rien à voir avec la taille de l'image, bref.

Avec getimagesize donc, on trouve les données "APP2" et on peut les décoder (j'ai créé une petite classe MPF pour s'en occuper). C'est relativement simple, c'est du TIFF donc on retrouve encore des segments, avec des marqueurs appelés des tags. Un de ces tags est "MPImageList" et contient des données sous un autre format assez basique défini par le format MPF.

On décode tout ça, on récupère l'offset de départ de l'image qui nous intéresse (celle dont le type est 0x10002 = Large Thumbnail (full HD equivalent)) et... bah merde, on a besoin de savoir à quel endroit dans l'image était le marqueur APP2 du départ, puisque tout est relatif à ce marqueur. Et getimagesize ne nous le dit pas.

Donc finalement, on doit lire nous-mêmes les métadonnées JPEG, heureusement c'est assez facile : il faut simplement retrouver les octets 0xFF 0xE2. Il pourrait probablement y en avoir plusieurs, mais je ne suis pas allé jusqu'à gérer ce cas, je prends seulement le premier. Les deux octets suivants indiquent la longueur des données, et c'est ainsi qu'on récupère nos données MPF, en connaissant cette fois-ci leur emplacement dans le fichier.

Bref, ça marche bien mais il fallait simplement tout faire soi-même. J'ai pu m'aider du livre Exiv2 qui est assez complet mais n'aborde pas le format MPF, des spécifications du format MPF (qui ne sont plus accessibles hors d'archive.org ?) mais surtout donc des informations sur fileformats.archiveteam.org, ainsi que du code suivant qui décode du TIFF en PHP : SRTMGeoTIFFReader.php.

@contact