focus on JSON generation

EMLG does not generate the HTML file anymore, users are invited to copy
the provided index.html and use it as they see fit.
main
dece 2 years ago
parent da48104702
commit cbe0446382

1
.gitignore vendored

@ -1,3 +1,4 @@
build/
dist/
*.egg-info/
__pycache__/

@ -1,7 +1,7 @@
EmlGallery
==========
EmlGallery (Elementary Masonry & Lightbox Gallery) is a simple generator for Web
EmlGallery (elementary Masonry & Lightbox Gallery) is a simple generator for Web
galleries, based on Masonry, a library that beautifully tiles images on a page,
and Lightbox, a library for presenting individual images nicely.
@ -16,31 +16,33 @@ Usage
-----
Point the script to a folder with photos and it generates a JSON file with
metadata and an index.html file to visit, push everything to a Web host and you
are ready to go.
metadata, push it alongside your photos to a Web host and you are ready to go.
```bash
emlg "/home/dece/Photos/2022 dubious trip to antartica"
ls $!
# → data.json index.html IMG1.jpg IMG2.jpg IMG3.jpg …
$ emlg "/home/dece/Photos/2022 dubious trip to antartica"
# "Data JSON saved."
$ ls $!
# → data.json IMG1.jpg IMG2.jpg IMG3.jpg …
```
As it relies on external libraries to work, you have to provide your own path to
the different libraries, either a relative path on your computer or server, or
an URL to some CDN. See the optional arguments below for the argument names.
The `index.html` file at the root of this repository is able to load the JSON
data and build the gallery when someone visits your page. There are two ways to
provide the JSON data to the gallery, explained below but also at the bottom of
`index.html` itself.
```
positional arguments:
dir directory with the photos
optional arguments:
-h, --help show this help message and exit
--title TITLE page title
--jquery JQUERY Web path to JQuery library
--masonry MASONRY Web path to Masonry library
--imagesloaded IMAGESLOADED
Web path to ImagesLoaded library
--lightbox LIGHTBOX Web path to Lightbox2 library
--lightbox-css LIGHTBOX_CSS
Web path to Lightbox2 CSS
```
### First method: provide data.json as an URL
Host the HTML file somewhere and link it with the URL to your data.json as the
fragment part of the URL (anything after the #).
Example: `http://gallery.dece.space/#http://unrelated.host/`
This method lets you host only one copy of the gallery page and provide
different links for each gallery. One drawback is that the server hosting your
images must have its CORS policy configured to let your browser load the photos.
### Second method: embed data.json into the page
Override some variables as explained in `index.html` and you should be good to
go. This method avoids the second request, but as you need to fetch the
thumbnails anyway…

@ -1,171 +1,62 @@
"""An elementary Web gallery generator using Masonry and Lightbox."""
import argparse
import json
import os
from argparse import ArgumentParser
from PIL import Image
SUPPORTED_TYPES = ("jpg", "jpeg", "png", "gif", "webp")
JQUERY_PATH = "/libs/jquery/jquery.min.js"
MASONRY_PATH = "/libs/masonry/masonry.pkgd.min.js"
IMAGESLOADED_PATH = "/libs/masonry/imagesloaded.pkgd.min.js"
LIGHTBOX_PATH = "/libs/lightbox2/js/lightbox.min.js"
LIGHTBOX_CSS_PATH = "/libs/lightbox2/css/lightbox.min.css"
HTML = """\
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
{css}
</style>
<!-- jQuery -->
<script src="{jquery}"></script>
<!-- Masonry & ImagesLoaded -->
<script src="{masonry}"></script>
<script src="{imagesloaded}"></script>
<!-- Lightbox2 -->
<link href="{lightbox_css}" rel="stylesheet">
<script src="{lightbox}"></script>
</head>
<body>
<div class="masonry">
</div>
<script>
{js}
</script>
</body>
</html>
"""
CSS = """\
body {
background: black;
}
/* 4 columns by default */
.masonry {
margin: auto;
width: 1470px;
}
.masonry-item img {
margin-bottom: 10px;
width: 360px;
}
/* 3 columns on small desktop screens */
@media only screen and (max-width: 1500px) {
.masonry { width: 1100px; }
}
/* 2 columns on medium-size screens */
@media only screen and (max-width: 1200px) {
.masonry { width: 730px; }
}
/* 1 column on mobile */
@media only screen and (max-width: 768px) {
.masonry { width: 100%; }
.masonry-item { width: 100%; }
.masonry-item img { width: 100%; }
}
"""
JS = """\
var dirname = path => path.replace(/\\\\/g,'/').replace(/\\/[^\\/]*$/, '');
function initMasonry(json) {
var $grid = $('.masonry');
for (var i = 0; i < json.length; i++) {
var entry = json[i];
var $img = $(document.createElement('img'));
$img.attr('src', entry.src);
var $link = $(document.createElement('a'));
$link.attr({
'href': entry.src,
'data-lightbox': 'gallery',
'data-title': entry.title,
});
$link.append($img);
var $container = $(document.createElement('div'));
$container.addClass('masonry-item');
$container.append($link);
$grid.append($container);
}
$grid.masonry({
itemSelector: '.masonry-item',
columnWidth: 360,
gutter: 10,
});
$grid.imagesLoaded().progress(() => $grid.masonry('layout'));
}
var loc = dirname(location.pathname) + '/data.json';
$(document).ready(() => {
fetch(loc)
.then(response => response.json())
.then(json => initMasonry(json));
});
"""
def main():
ap = ArgumentParser(description="Generate JSON metadata from images.")
ap.add_argument("dir", help="directory with the images to use")
ap.add_argument("--no-thumbnails", action="store_true", help="disable thumbnail generation")
args = ap.parse_args()
gen_gallery(args.dir, not bool(args.no_thumbnails))
def gen_gallery(dirpath: str, title: str, libs: dict):
def gen_gallery(dir_path: str, generate_thumbnails: bool = True):
"""Generate the JSON metadata for the images found at dirpath."""
entries = []
for filename in os.listdir(dirpath):
ext = os.path.splitext(filename)[1][1:].lower()
if ext not in SUPPORTED_TYPES:
for file_name in sorted(os.listdir(dir_path)):
name, ext = os.path.splitext(file_name)
if name.endswith("_t"): # do not add already generated thumbnails
continue
if ext[1:].lower() not in SUPPORTED_TYPES:
continue
image = Image.open(os.path.join(dirpath, filename))
image = Image.open(os.path.join(dir_path, file_name))
dimensions = image.size
entries.append({
"src": filename,
entry = {
"src": file_name,
"w": dimensions[0],
"h": dimensions[1],
"title": "",
})
}
if generate_thumbnails:
try:
entry["thumb"] = gen_thumbnail(image, file_name, dir_path)
except OSError as exc:
exit(f"Can't create thumbnail: {exc}")
entries.append(entry)
try:
with open(os.path.join(dirpath, "data.json"), "wt") as data_file:
with open(os.path.join(dir_path, "data.json"), "wt") as data_file:
json.dump(entries, data_file)
except OSError as exc:
exit(f"Can't write data.json file: {exc}")
print("Data JSON saved.")
try:
with open(os.path.join(dirpath, "index.html"), "wt") as index_file:
index_file.write(HTML.format(title=title, css=CSS, js=JS, **libs))
except OSError as exc:
exit(f"Can't write index.html file: {exc}")
print("Web page saved.")
def main():
ap = argparse.ArgumentParser()
ap.add_argument("dir", help="directory with the photos")
ap.add_argument("--title", default="EmlGallery", help="page title")
ap.add_argument("--jquery", help="Web path to JQuery library")
ap.add_argument("--masonry", help="Web path to Masonry library")
ap.add_argument("--imagesloaded", help="Web path to ImagesLoaded library")
ap.add_argument("--lightbox", help="Web path to Lightbox2 library")
ap.add_argument("--lightbox-css", help="Web path to Lightbox2 CSS")
args = ap.parse_args()
libs = {
"jquery": args.jquery or JQUERY_PATH,
"masonry": args.masonry or MASONRY_PATH,
"imagesloaded": args.imagesloaded or IMAGESLOADED_PATH,
"lightbox": args.lightbox or LIGHTBOX_PATH,
"lightbox_css": args.lightbox_css or LIGHTBOX_CSS_PATH,
}
gen_gallery(args.dir, args.title, libs)
def gen_thumbnail(image: Image.Image, file_name: str, dir_path: str) -> str:
"""Generate a thumbnail from this image, return its name."""
thumbnail = image.copy()
thumbnail.thumbnail((360, 720), resample=Image.Resampling.LANCZOS)
name, ext = os.path.splitext(file_name)
thumbnail_file_name = name + "_t" + ext
thumbnail_path = os.path.join(dir_path, thumbnail_file_name)
thumbnail.save(thumbnail_path)
return thumbnail_file_name
if __name__ == "__main__":

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<style>
body { background: black; }
/* 4 columns by default */
.masonry { margin: auto; width: 1470px; }
.masonry-item img { margin-bottom: 10px; width: 360px; }
/* 3 columns on small desktop screens */
@media only screen and (max-width: 1500px) { .masonry { width: 1100px; } }
/* 2 columns on medium-size screens */
@media only screen and (max-width: 1200px) { .masonry { width: 730px; } }
/* 1 column on mobile */
@media only screen and (max-width: 768px) {
.masonry { width: 100%; }
.masonry-item { width: 100%; }
.masonry-item img { width: 100%; }
}
</style>
<!-- Replace the following link with your own if you do not wish to use CDNs. -->
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.3/css/lightbox.min.css"
integrity="sha512-ZKX+BvQihRJPA8CROKBhDNvoc2aDMOdAlcm7TUQY+35XYtrd3yh95QOOhsPDQY9QnKE0Wqag9y38OIgEvb88cA=="
crossorigin="anonymous"
referrerpolicy="no-referrer" />
</head>
<body>
<div class="masonry"></div>
<!-- Replace the following links with your own if you do not wish to use CDNs. -->
<!-- jQuery -->
<script
src="https://code.jquery.com/jquery-3.6.1.min.js"
integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ="
crossorigin="anonymous"></script>
<!-- Masonry & ImagesLoaded -->
<script src="https://unpkg.com/masonry-layout@4.2.2/dist/masonry.pkgd.min.js"></script>
<script src="https://unpkg.com/imagesloaded@5/imagesloaded.pkgd.min.js"></script>
<!-- Lightbox2 -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.3/js/lightbox.min.js"
integrity="sha512-k2GFCTbp9rQU412BStrcD/rlwv1PYec9SNrkbQlo6RZCf75l6KcC3UwDY8H5n5hl4v77IDtIPwOk9Dqjs/mMBQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"></script>
<script>
function initMasonry(json, baseUrl) {
var $grid = $('.masonry');
for (var i = 0; i < json.length; i++) {
var entry = json[i];
var $img = $(document.createElement('img'));
var imageSrc = baseUrl + entry.src;
var thumbSrc = entry.thumb ? baseUrl + entry.thumb : imageSrc;
$img.attr('src', thumbSrc);
var $link = $(document.createElement('a'));
$link.attr({
'href': imageSrc,
'data-lightbox': 'gallery',
'data-title': entry.title,
});
$link.append($img);
var $container = $(document.createElement('div'));
$container.addClass('masonry-item');
$container.append($link);
$grid.append($container);
}
$grid.masonry({
itemSelector: '.masonry-item',
columnWidth: 360,
gutter: 10,
});
$grid.imagesLoaded().progress(() => $grid.masonry('layout'));
}
$(document).ready(() => {
// Uncomment this block to load image data remotely, using the URL
// in the current URL fragment.
/*/
var dataUrl = location.hash.substring(1);
var dirUrl = dataUrl.substring(0, dataUrl.lastIndexOf("/") + 1);
fetch(location.hash.substring(1))
.then(response => response.json())
.then(json => initMasonry(json, dirUrl));
//*/
// Uncomment this block to use embed data.
/*/
var data = {}; // set this to the content of data.json.
var dirUrl = ""; // set this to the directory where images are stored.
initMasonry(data, dirUrl);
//*/
});
</script>
</body>
</html>

@ -1,6 +1,6 @@
[metadata]
name = emlg
version = 0.0.1
version = 0.0.2
description = Minimal HTML/JS photo gallery
long_description = file: README.md
long_description_content_type = text/markdown
@ -16,6 +16,8 @@ classifiers =
packages = emlg
python_requires = >= 3.7
setup_requires = setuptools >= 38.3.0
install_requires =
Pillow
[options.entry_points]
console_scripts =

Loading…
Cancel
Save