Static photo albums with llgal

This commit is contained in:
Florian Obser 2023-01-29 11:46:34 +01:00
parent 4ccdbb7525
commit 13add8d58a
3 changed files with 607 additions and 0 deletions

162
llgal.org Normal file
View File

@ -0,0 +1,162 @@
#+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 and then
setup a =~[[file:llgal/llgalrc.txt][/.llgal/llgal.rc]]= file.
These are the things I configured:
#+begin_export ascii
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_export
** 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} -vframes 1 -vf "scale=240:-1" \
.llgal/my_thump_${i}.jpg
done
#+end_src
We grab one frame (=-vframes 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_export ascii
# Additional prefix of user-provided scaled images
# user_scaled_image_filenameprefix = "my"
# Additional prefix of user-provided thumbnails
# user_thumbnail_image_filenameprefix = "my"
#+end_export
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_export ascii
MVI: VID_20230120_124000.mp4 ---- Open movie VID_20230120_124000.mp4 ----
#+end_export
And we can change it to this to show the thumbnail[fn::I am using an
emacs macro for that. YMMV.]:
#+begin_export ascii
MVI: VID_20230120_124000.mp4 ---- <img src=".llgal/my_thump_VID_20230120_124000.mp4.jpg" /><br /> Open movie ----
#+end_export
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.

383
llgal/llgalrc.txt Normal file
View File

@ -0,0 +1,383 @@
# llgal configuration options.
# These options may be defined in /etc/llgal/llgalrc, a per-user
# ${HOME}/.llgal/llgalrc file or a directory-specific .llgal/llgalrc.
# Most of these options may later be overridden by command line options.
# Default are shown in examples below.
# Syntax is one of these:
# variable = "string"
# variable = decimal number
# variable = 0 or 1 for disabling or enabled
# For now, only static values are supported.
# Brackets indicate corresponding command line options.
##### Name of generic llgal files #####
# Name of the captions header file that is inserted at the beginning
# of generated captions files.
# captions_header_filename = "captions.header"
# Name of the CSS file.
# Such a file will be stored under $local_llgal_dir.
# css_filename = "llgal.css"
# Name of the film tile image.
# Such a file will be stored under $local_llgal_dir.
# Note that this filename must match the one that is used in the CSS file.
# filmtile_filename = "tile.png"
# Name for image link to the index, previous or next slide
# index_link_image_filename = "index.png"
# prev_slide_link_image_filename = "prev.png"
# next_slide_link_image_filename = "next.png"
# Name of the index template.
# Such a template file will be used during gallery generation.
# indextemplate_filename = "indextemplate.html"
# Name of the slide template.
# Such a template file will be used during gallery generation.
# slidetemplate_filename = "slidetemplate.html"
##### Location of llgal files if available on the web #####
# Location of the CSS file if available on the web [--uc <url>].
# If ending with '/', css_filename will be added.
# css_location = ""
# Location of llgal image files if available on the web.
# If ending with '/', their generic filename will be added.
# These locations may be set alltogether with --ui <url>.
# filmtile_location = ""
# index_link_image_location = ""
# prev_slide_link_image_location = ""
# next_slide_link_image_location = ""
##### Location and name of generated files #####
# Local subdirectory where llgal files generated files will be stored.
# This option is only available in system- and user-wide configuration
# files.
# Note that HTML files are generated in place while captions and CSS
# are kept in this subdirectory.
# Gallery specific HTML templates and llgalrc configuration file
# might also be defined here.
# local_llgal_dir = ".llgal"
# Name of the generated index file [-i <s>]
# index_filename = "index"
# Prefix of generated HTML slide filenames
# Note that this prefix is used to decide what HTML to delete when --clean
# is passed. Setting this option to an empty string will make llgal remove
# all HTML files.
# slide_filenameprefix = "slide_"
# Prefix used to determine the filenames of scaled image that are
# shown in slides (in case of --sx or --sy).
# These files will be stored under $local_llgal_dir.
# scaled_image_filenameprefix = "scaled_"
# Prefix used to determine thumbnail filenames from original images.
# These files will be stored under $local_llgal_dir.
# thumbnail_image_filenameprefix = "thumb_"
# Name of the captions file that will be generated when llgal is called
# with --gc. This file will be stored under $local_llgal_dir.
# captions_filename = "captions"
# Additional prefix of user-provided scaled images
# user_scaled_image_filenameprefix = "my"
# Additional prefix of user-provided thumbnails
# user_thumbnail_image_filenameprefix = "my"
# Character to use to replace / in the thumbnail/scaled of subdir images
# path_separator_replacement = "@"
##### Index #####
# Cellpadding in the index table [-p <n>]
# Must be >= 0, setting to < 0 resets to default
# index_cellpadding = 3
# Display links and text as regular text instead of thumbnails
# in the main gallery thumbnail index [-L]
# list_links = 0
# Pixels per row of thumbnails in index [--wx <n>]
# Must be > 0, 0 means unlimited, setting to < 0 resets to default
# pixels_per_row = 0
# Thumbnails per row in index [-w <n>]
# Must be > 0, 0 means unlimited, setting to < 0 resets to default
thumbnails_per_row = 3
# Do not output any absolute thumbnail sizes in HTML code and assume the
# CSS style sheet will take care of it (in table.index [td.image-slide [img]])
# slide_dimensions_from_css = 0
# Maximal height of thumbnails [--ty <n>]
# Must be > 0, setting to < 0 resets to default
# Changing this value does not affect the maximal width (see thumbnail_width_max)
thumbnail_height_max = 240
# Maximal width of thumbnails [--tx <n>]
# Must be > 0, 0 means unlimited, setting to < 0 resets to default
# Changing this value does not affect the maximal height (see thumbnail_height_max)
thumbnail_width_max = 240
# Write captions under thumbnails [-u]
# show_caption_under_thumbnails = 0
# Show a film effect in the index of thumbnails [--fe]
# show_film_effect = 0
# Make thumbnail links point to the target object instead of
# the corresponding slide
MVI_link_to_target = 1
# FIL_link_to_target = 0
DIR_link_to_target = 1
# LNK_link_to_target = 0
##### Slides #####
# Make no slides [-s]
# make_no_slides = 0
# Use filename as slide filename [-n]
# make_slide_filename_from_filename = 0
# Also use extension in the slide filename to differenciate
# slide filename of images that have same filenames.
# make_slide_filename_from_extension = 0
# Do not output any absolute image size in the HTML code and assume the
# CSS style sheet will take care of it (in table.slide td.image-slide img)
# slide_dimensions_from_css = 0
# Maximal width of slides [--sx <n>]
# Must be > 0, 0 means unlimited, setting to < 0 resets to default
slide_width_max = 700
# Maximal height of slides [--sy <n>]
# Must be > 0, 0 means unlimited, setting to < 0 resets to default
slide_height_max = 700
# Default size of non-image slides.
# Must be > 0, setting to < 0 resets to default
# Note that the --sx and --sy options may lead to reduction of these values.
# text_slide_width = 400
# text_slide_height = 300
# Use an image instead of a text label for the link to the index,
# previous or next slide [--li]
# index_link_image = 0
# next_slide_link_image = 0
# prev_slide_link_image = 0
# Use a thumbnail preview instead of a text label for the link
# to the previous or next slide [--lt]
# next_slide_link_preview = 0
# prev_slide_link_preview = 0
# Generate slide titles from captions [-k]
# make_slide_title_from_caption = 0
# Generate links between last and first slides or galleries.
# link_between_last_and_first = 1
# Display a table of EXIF tags under each image slide [--exif]
# The tags are given with their name in exiftool -list and separated
# with a comma.
# show_exif_tags = ""
# Display a table of all available EXIF tags under each image slide [--exif]
show_all_exif_tags = 1
##### Captions #####
# This line will be added to the captions file llgal will generate when
# called with --gc. If the user doesn't want llgal to remove this captions
# file when called with --clean, it just needs to remove this line from
# the file.
# captions_removal_line = "REMOVE THIS LINE IF LLGAL SHOULD NOT REMOVE THIS FILE"
# Generate captions from comment stored in images [--cc [<s>]]
# make_caption_from_image_comment = ""
# Also generate captions from timestamp stored in images [--ct]
# make_caption_from_image_timestamp = 0
# Generate captions from filenames without their extension [--cf]
# make_caption_from_filename = 0
# Generate captions from filenames with their extension
# make_caption_from_extension = 0
# Show dimensions and/or size of the images and movies [-a, --ad, --as]
# show_dimensions = 0
# show_size = 0
# Change the format of the counter shown on the slides
# %n is replaced by the slide number, %0n gets leading zeros,
# and %t is replaced by the number of slides.
# Setting to "" disables the slide counter [--nc]
# slide_counter_format = "&nbsp;&nbsp;&nbsp;(%0n/%t)"
##### Text #####
# Default title of the gallery.
# May be overridden with [--title <s>] or TITLE: in the captions file.
# index_title_default = "Index of pictures"
# Change text in links to parent directory.
# parent_gallery_link_text = "Back to parent gallery"
# Change text in links to previous gallery.
# prev_gallery_link_text = "Previous gallery "
# Change text in links to next directory.
# next_gallery_link_text = "Next gallery "
# Label of the link from a slide to the index
# index_link_text = "Index"
# Label of the link from a slide to the previous one
# prev_slide_link_text = "&larr;"
# Label of the link from a slide to the next one
# next_slide_link_text = "Next&gt;&gt;"
# Text prefixing the filename when generating link text
# for movies without a captions file.
# MVI_link_text = "Open movie "
# Text prefixing the filename when generating link text
# for files without a captions file.
# FIL_link_text = "Download file "
# Text prefixing the filename when generating link text
# for directories without a captions file.
# DIR_link_text = "Open subgallery "
# Text shown as image alternative for full-size images in slides
# alt_full_text = ""
# Text shown as image alternative for scaled images in slides
# alt_scaled_text = "Scaled image "
# Text shown as image alternative for thumbnails in the index
# alt_thumbnail_text = "Thumbnail "
# Text shown as an image alternative for the film tile in the index
# alt_film_tile_text = "Film tile"
# Text shown when the mouse pointer is over a scaled image in a slide
# over_scaled_text = "Click to see full size "
# Text shown when the mouse pointer is over a thumbnail
# over_thumbnail_text = "Click to enlarge "
# Text shown when the mouse pointer is over a link from a slide to the index
# over_index_link_text = "Return to the index"
# Text shown when the mouse pointer is over a link from a slide to the previous one
# over_prev_slide_link_text = "Previous slide "
# Text shown when the mouse pointer is over a link from a slide to the next one
# over_next_slide_link_text = "Next slide "
# Unit to be used when printing sizes [--asu <s>]
# show_size_unit = "kB"
# Set timestamp format in captions (when enabled) using strftime format [--ct <s>]
# timestamp_format_in_caption = "%Y-%m-%d %H:%M:%S"
# Credits line at the bottom of the index
credits_text = "Copyright © 2014 - 2023 Florian Obser. All rights reserved."
##### What files to insert in the gallery #####
# Extensions that are matched when searching images (|-separated list)
# image_extensions = "jpg|jpeg|gif|png|tif|tiff|bmp|webp"
# Extensions that are matched when searching movies (|-separated list)
# movie_extensions = "mpg|mpeg|avi|mov|ogm|wmv|mp4|3gp|webm"
# Add all files to the list of entries
# not only images and movies [-A]
# add_all_files = 0
# Add subdirectories to the list of entries [-S]
# add_subdirs = 0
# Exclude files whose name matches [--exclude <s>]
# This option may be used several times.
exclude = "^js$"
# Include files whose name matches and were previously excluded [--include <s>].
# This option may be used several times.
# The order of includes and excludes is respected.
# include = "regexp"
# Sort criteria when scanning files in the working directory [--sort]
sort_criteria = "revtime"
##### Sections #####
# Add a new subdirectory to the list of sections [-P]
# section_dir = "subdir"
# Add all subdirectories to the list of sections [--Pall]
# recursive_sections = 0
# Add the subdirectory name as a title at the beginning of each section [--Ps]
# entitle_sections = 0
# Add a horizontal line at the beginning of each section in the index [--Ps]
# separate_sections = 0
##### Recursion #####
# Run recursively in subdirectories [-R]
recursive = 1
# Add links between subgalleries [--Rl]
link_subgalleries = 1
# Add links to the parent directory [--parent-gal]
# parent_gallery_link = 0
##### Various #####
# Additional configuration file [--config <s>]
# This option may be used multiple times.
# additional_configuration_file = "my_llgal.rc"
# Additional template directories [--templates].
# This option may be used multiple times.
# additional_template_dir = "path"
# Codeset to be set in HTML headers [--codeset <s>]
# codeset = "codeset"
# Language to be used for generated text in HTML pages [--lang <s>]
# If set, the LANGUAGE environment variable might prevent this option from working.
# language = "locale"
# Command to be used to generate scaled images and thumbnails
# thumbnail_create_command = "convert -scale <MAXW>x<MAXH> -- <IN> <OUT>"
# scaled_create_command = "convert -scale <MAXW>x<MAXH> -- <IN> <OUT>"
# Force regeneration of thumbnails and scaled images [-f].
# force_image_regeneration = 0
# Print notice messages [-v]
# verbose = 0
# Give access rights for www access [--www]
# www_access_rights = 0
# Extension of generate webpages [--php]
# www_extension = "html"

View File

@ -0,0 +1,62 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=LLGAL-CODESET" />
<meta name="generator" content="llgal LLGAL-OPTIONS" />
<title><!--SLIDE-TITLE--></title>
<link rel="stylesheet" type="text/css" href="<!--CSS-->" />
<link rel="contents" href="<!--INDEX-FILE-->" />
<link rel="index" href="<!--INDEX-FILE-->" />
<link rel="next" href="<!--NEXT-SLIDE-->" />
<link rel="previous" href="<!--PREV-SLIDE-->" />
<link rel="prefetch" href="<!--NEXT-SLIDE-->" />
<script type="text/javascript" src="/js/mousetrap.min.js"></script>
<script type="text/javascript" src="/js/hammer.min.js"></script>
</head>
<body>
<!-- Please note that the comments in ALL CAPS are the places where llgal
inserts the appropriate file names. You may move them around (or
remove them) to customize your slides at will. To see how this
works just compare the template file with a generated slide. -->
<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>
<div class="center" id="slide-container">
<!--THIS-SLIDE-->
</div>
<p class="caption">
<!--IMAGE-CAPTION-->
</p>
<div class="center">
<!--EXIF-TABLE-->
</div>
<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>
</body>
</html>