#+TITLE: Static photo albums with llgal
#+DATE: 2023-01-29
* Prologue
I used to create photo albums on [[https://flickr.com/photos/tlakh/][flickr]] for our vacations. I even had
a paid pro account for a few years. While I have backups of all my
photos, it still felt off to show these albums using somebody else's
computer. They might disappear any day. Since I have the storage
capacity and some time to waste I set out to self-host photo albums.
* Nextcloud Photos
My first layer of photo backups is a self-hosted [[https://nextcloud.com/][Nextcloud]]. I am using
the Nextcloud android app to automatically upload pictures when on
WiFi. So I might as well use the built-in Nextcloud Photos 2.0[fn::Why
is there not way to link to a description of this thing? There is a
GitHub page and it's described on [[https://nextcloud.com/athome/][https://nextcloud.com/athome/]] but
you can't direct-link to it‽].

Unfortunately it's not quite there yet. In increasing order of
annoyance:
1) Video buffering is not a thing.
2) Videos do not get a preview image.
3) It is very confused about ordering. The order in the photo app is
   correct but when added to the gallery sometimes two photos switch
   places. There is some mumbling about mtime vs. ctime vs. exiff data
   in some forum, but that's not the problem here. First of all, no
   matter how Nextcloud sorts the picture it would always arrive at
   the same ordering. I did not edit the photos, so mtime and ctime
   are the same. But also note that the order in the photo app is
   correct. It only gets confused when photos are added to an
   album. The photo album tells a story. It is really bad if the
   "arrival at home" photo is shown before the "last night of the
   vacation" photo.
4) The absolute deal-breaker is that it does not work though. The
   whole idea is to share a link to the album for anonymous users. You
   can do that, and it shows you the album with thumbnails. But as
   soon as you click on a photo or start the slide show it wants you
   to login. I suspect you need to enable sharing via link to every
   photo in the album to make it work. There does not seem to be a way
   to do this in bulk so I did not try it. I have also found a
   discussion online where people argue for years along the lines of
   "it does not work" - "yes it does" - "no it does not". That was
   tiresome and they are still going on. There was some mumbling that
   you need to enable federation in Nextcloud to make anonymous albums
   work, but I have that on and it still does not work.
* llgal
I then remembered that I had used a static photo album generator
before: [[http://bgoglin.free.fr/llgal/][llgal]]. But I never tried to make it "pretty" or convert all my
flickr albums to it.
** Setup
llgal is in OpenBSD ports, so it can be installed with ~pkg_add~. I
store all my albums as sub-folders in ~/var/www/img.sha256.net/~ and
then run ~llgal~. I was experimenting a bit with the command line
flags to figure out what I need.

I then setup a =~/.llgal/llgalrc= file. With that in place I can run
~llgal~ in the top level photos directory and it will (re-)create all
albums as needed.

I downloaded a sample file from [[https://github.com/bgoglin/llgal/blob/master/doc/llgalrc][GitHub]] and edited for my needs. You
can see the resulting file [[file:llgal/llgalrc.txt][here]].

These are the things I configured:
#+begin_src
thumbnails_per_row = 3
thumbnail_height_max = 240
thumbnail_width_max = 240
MVI_link_to_target = 1
DIR_link_to_target = 1
slide_width_max = 700
slide_height_max = 700
show_all_exif_tags = 1
credits_text = "Copyright © 2014 - 2023 Florian Obser. All rights reserved."
exclude = "^js$"
sort_criteria = "revtime"
recursive = 1
link_subgalleries = 1
#+end_src
** Video thumbnails
llgal does not create thumbnails for video files (yet) so I had to
hack around that a bit.
First we use ~ffmpeg~ to create the thumbnails:
#+begin_src shell
  for i in *.mp4; do
      ffmpeg -ss 1 -i ${i} -frames:v 1 -vf "scale=240:-1" \
             .llgal/my_thump_${i}.jpg
  done
#+end_src
We grab one frame (=-frames:v 1=), one second into the video (=-ss
1=), and scale it to 240 pixels wide while keeping the aspect ratio
(=-vf "scale=240:-1"=). We store the thumbnail in the ~.llgal~
sub-directory, where ~llgal~ stores its own thumbnails and scaled
images. The config file has this:
#+begin_src
# Additional prefix of user-provided scaled images
# user_scaled_image_filenameprefix = "my"

# Additional prefix of user-provided thumbnails
# user_thumbnail_image_filenameprefix = "my"
#+end_src
I have not worked out what that does, but I am using the "my" prefix
because of this.

llgal does not pick up these thumbnails but we can use the /captions/
file to show them. First we are creating /captions files in all
albums: ~llgal --gc~. This creates =.llgal/captions= in all albums.

It creates lines for all photos and videos that we can edit, for
videos it looks like this:
#+begin_src
MVI: VID_20230120_124000.mp4 ---- Open movie VID_20230120_124000.mp4 ----
#+end_src
And we can change it to this to show the thumbnail[fn::I am using an
emacs macro for that. YMMV.]:
#+begin_src
MVI: VID_20230120_124000.mp4 ---- <img src=".llgal/my_thump_VID_20230120_124000.mp4.jpg" /><br /> Open movie ----
#+end_src
I am using the same trick to have thumbnails for the top-level [[https://img.sha256.net/index.html][album
overview]]. The only difference is that it is a =DIR= line instead of
=MVI=.
** Keyboard and swipe navigation.
llgal links to [[http://santana2000.com/][santana2000.com]] as an example. Those albums use
[[https://hammerjs.github.io/][hammer.js]] for swipe navigation and [[https://craig.is/killing/mice][mousetrap]] for keyboard navigation.
I have copied =slidetemplate.html= from =/usr/local/share/llgal= to
=~/.llgal= and edited it: [[file:llgal/slidetemplate.html.txt][slidetemplate.html]].
We need to include the JavaScript files in the header:
#+begin_src html
  <script type="text/javascript" src="/js/mousetrap.min.js"></script>
  <script type="text/javascript" src="/js/hammer.min.js"></script>
#+end_src
Set the =id= for the navigation links:
#+begin_src html
  <p class="center">
    <a id="prevslide" href="<!--PREV-SLIDE-->"><!--PREV-SLIDE-LINK-TEXT--></a>
    &nbsp; &nbsp; &nbsp;
    <a id="indexlink" href="<!--INDEX-FILE-->"><!--INDEX-LINK-TEXT--></a>
    &nbsp; &nbsp; &nbsp;
    <a id="nextslide" href="<!--NEXT-SLIDE-->"><!--NEXT-SLIDE-LINK-TEXT--></a>
  </p>
#+end_src
And then hook up JavaScript to the navigation links:
#+begin_src html
  <script type="text/javascript">
      // Script for keyboard navigation
      var prev = document.getElementById("prevslide").href;
      var next = document.getElementById("nextslide").href;
      Mousetrap.bind('left',  function () { location.href = prev; });
      Mousetrap.bind('right', function () { location.href = next; });
      Mousetrap.bind('h',     function () { location.href = indexlink; });
      Mousetrap.bind('j',     function () { location.href = prev; });
      Mousetrap.bind('k',     function () { location.href = next; });
  </script>

  <script type="text/javascript">
      // Script for touch gestures (Hammerjs)
      var slide = document.getElementById('slide-container');
      var mc = new Hammer(slide);
      mc.on("swiperight", function(ev) { location.href = next; });
      mc.on("swipeleft",  function(ev) { location.href = prev; });
  </script>
#+end_src
This seems to disable pinch-zooming on the image itself on android. I
have not figured out why that is. The hammerjs documentation talks
about pinch, but my understanding is that it should just work. It does
not though...
* Epilogue
This was not a lot of work, please enjoy the first [[https://img.sha256.net/2023-01%20Malta/index.html][album from our trip
to Malta in 2023]].
Time permitting I will convert more albums from flickr and google
photos in the future.
