13

I just want to be able to share a link to several images at once.

squirrel
  • 5,114
  • 4
  • 31
  • 43

5 Answers5

17

The most simple way would be transforming file listings of nginx. You can do that by making nginx output listings as XML and then transform them using XSLT. Built-in module ngx_http_autoindex_module will do the former and usually dynamic module ngx_http_xslt_filter_module (aka ngx_http_xslt_module) will do the latter.

First, load the module in nginx.conf if needed:

load_module "/usr/lib/nginx/modules/ngx_http_xslt_filter_module.so";

Then, in your sites-available/website.com, add a location that tells nginx to transform the xml index using stlylesheet gal.xslt and pass a the name of the folder as a parameter.

location ~ /gal/([A-z]+)/$ {
    autoindex on;
    autoindex_format xml;
    xslt_string_param title $1;
    xslt_stylesheet gal.xslt;
    try_files $uri/ =404;
}

Finally, create gal.xslt in /etc/nginx that says,

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8" indent="yes" />
<xsl:template match="/">
    <xsl:text disable-output-escaping='yes'>&lt;!DOCTYPE html&gt;</xsl:text>
    <html>
    <head>
        <title><xsl:value-of select="$title" /></title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <style>
        img, video {
            display: block;
            max-width: 20cm;
            max-height: 20cm;
            margin: 2mm;
            vertical-align: bottom;
            image-orientation: from-image;
        }
        @media all and (max-width: 20.4cm) {
            img {
                max-width: calc(100% - 4mm);
            }
        }
        body {
            margin: 0;
        }
        </style>
    </head>
    <body>
        <xsl:for-each select="list/file">
            <xsl:choose>
                <xsl:when test="contains(' mp4 webm mkv avi wmv flv ogv ', concat(' ', substring-after(., '.'), ' '))">
                    <video controls="" src="{.}" alt="{.}" title="{.}"/>
                </xsl:when>
                <xsl:otherwise>
                    <img src="{.}" alt="{.}" title="{.}"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </body>
    </html>
</xsl:template>
</xsl:stylesheet>

Now put some images into /var/www/html/gal/foo, restart nginx, navigate to website.com/gal/foo and you will see a simple but usable and responsive image gallery.

squirrel
  • 5,114
  • 4
  • 31
  • 43
7

Thanks @squirrel that's really simply yet powerful.

I've tweaked the xslt - this version gal.xslt below:

  • Displays tumbnails in a grid, four columns wide.
  • Resizes according to page size.
  • Each image is click-able, so you can see the full size image.

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="utf-8" indent="yes" />
    <xsl:template match="/">
    <xsl:text disable-output-escaping='yes'>&lt;!DOCTYPE html&gt;</xsl:text>
    <html>
    <head>
        <title><xsl:value-of select="$title" /></title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <style>
        img {
             display: inline;
             width: 23%;    
             margin: 2mm;
             vertical-align: bottom;
        }
        @media all and (max-width: 20.4cm) {
            img {
                max-width: calc(100% - 4mm);
            }
        }
        body {
            margin: 0;
        }
        </style>
    </head>
    <body>
        <xsl:for-each select="list/file">
            <a href="{.}" title="click to enlarge">
                <img src="{.}" alt="{.}"/>
            </a>
       </xsl:for-each>
    </body>
    </html>
    </xsl:template>
    </xsl:stylesheet>
    

Cheers,

Greg

  • 1
    Nice job, next step is to mix in the image filter module to generate and cache the thumbnails server side ;) – miknik Aug 28 '18 at 23:04
2

I tweaked a bit more: fancybox, thumbnail handling (with pregenerated thumbnails in the thumbs directory), ability to download all images as a browser generated zip file.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8" indent="yes" />
<xsl:template match="/">
<xsl:text disable-output-escaping='yes'>&lt;!DOCTYPE html&gt;</xsl:text>
<html>
<head>
  <title><xsl:value-of select="$title" /></title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" integrity="sha256-Vzbj7sDDS/woiFS3uNKo8eIuni59rjyNGtXfstRzStA=" crossorigin="anonymous"/>
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js" integrity="sha256-yt2kYMy0w8AbtF89WXb2P1rfjcP/HTHLT7097U8Y5b8=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/jszip@3.1.5/dist/jszip.min.js" integrity="sha256-PZ/OvdXxEW1u3nuTAUCSjd4lyaoJ3UJpv/X11x2Gi5c=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js" integrity="sha256-xoh0y6ov0WULfXcLMoaA6nZfszdgI8w2CEJ/3k8NBIE=" crossorigin="anonymous"></script>
  <style>img { display: block; }</style>
</head>
<body>
<h1 style="text-align: center;"><xsl:value-of select="$title"/></h1>
<div style="border-bottom: 1px solid gray; margin-bottom: 1rem;"></div>
<div style="display: flex; flex-wrap: wrap; gap: 2px; justify-content: center;">
<xsl:for-each select="list/file">
  <a href="{.}" data-fancybox="gallery">
  <xsl:choose>
  <xsl:when test="count(/list/directory[text() = 'thumbs'])">
    <img loading="lazy" src="thumbs/{.}"/>
  </xsl:when>
  <xsl:otherwise>
    <img loading="lazy" src="{.}" height="200"/>
  </xsl:otherwise>
  </xsl:choose>
  </a>
</xsl:for-each>
</div>
<script>
async function downloadAll() {
  const zip = JSZip();
  const folder = zip.folder('<xsl:value-of select="$title" />');
  const files = [
    <xsl:for-each select="list/file">
     '<xsl:value-of select="." />',
    </xsl:for-each>
  ];
  for(const i in files) {
    const file = files[i];
    const resp = await fetch(file);
    folder.file(file, resp.blob());
    $.fancybox.animate($.fancybox.getInstance().SlideShow.$progress.show(),{scaleX: i/files.length}, 0.1);
  }
  const zipFile = await zip.generateAsync({type: 'blob'});
  saveAs(zipFile, '<xsl:value-of select="$title" />' + '.zip');
  $.fancybox.animate($.fancybox.getInstance().SlideShow.$progress.show(),{scaleX: 0}, 0.1); 
}

$('[data-fancybox="gallery"]').fancybox({
  buttons: [
    "zoom",
    "slideShow",
    "fullScreen",
    "download",
    "downloadAll",
    "close"
  ],
  btnTpl: {
    downloadAll:
      '<a class="fancybox-button fancybox-button--download" title="Download All" href="javascript:downloadAll()">' +
      '<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3M3 17V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" style="fill:unset; stroke-width:2"/></svg>' +
      '</a>',
 },
});
$('[data-fancybox="gallery"]')[0].click();
</script>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
soyer
  • 316
  • 2
  • 10
1

I tweaked also a bit more :-) and prepare generated configuration with the JS and CS. Main features are:

  • images...
    • reading EXIF (via exifr)
    • show GPS on the Google Maps
    • view photo sphere panoramas (via Photo Sphere Viewer)
    • an optional automatic image thumbnails with an optional cache
  • videos (via Plyr) with optional auto conversion SRT subtitles to supported VTT
  • other files (just view and download)
  • sharing link to the gallery
  • sharing on social networks (via shareon)
  • as lightbox is used GLightbox
  • HTTP or HTTPS (Let's encrypt support - via external tools - dehyhrated for example)
  • custom gallery title/favicon and many more settings
  • password access (via HTTP Basic Auth)

You can check it on https://github.com/forrest79/StaticNginxGallery.

Forrest79
  • 56
  • 4
0

@squirrel's fantastic answer inspired me to bundle this all into a container for easy deployment. https://github.com/stuart23/image_server

Instructions are in the readme - it is as simple as:

docker build -t image_server .
docker run -p 8000:8000 -v /local/path/to/gallery:/var/www image_server
Stuart Buckingham
  • 1,574
  • 16
  • 25