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.
This commit is contained in:
dece 2022-09-04 18:26:20 +02:00
parent da48104702
commit cbe0446382
6 changed files with 168 additions and 167 deletions

1
.gitignore vendored
View file

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

View file

@ -1,7 +1,7 @@
EmlGallery 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, galleries, based on Masonry, a library that beautifully tiles images on a page,
and Lightbox, a library for presenting individual images nicely. 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 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 metadata, push it alongside your photos to a Web host and you are ready to go.
are ready to go.
```bash ```bash
emlg "/home/dece/Photos/2022 dubious trip to antartica" $ emlg "/home/dece/Photos/2022 dubious trip to antartica"
ls $! # "Data JSON saved."
# → data.json index.html IMG1.jpg IMG2.jpg IMG3.jpg … $ 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 `index.html` file at the root of this repository is able to load the JSON
the different libraries, either a relative path on your computer or server, or data and build the gallery when someone visits your page. There are two ways to
an URL to some CDN. See the optional arguments below for the argument names. provide the JSON data to the gallery, explained below but also at the bottom of
`index.html` itself.
``` ### First method: provide data.json as an URL
positional arguments:
dir directory with the photos
optional arguments: Host the HTML file somewhere and link it with the URL to your data.json as the
-h, --help show this help message and exit fragment part of the URL (anything after the #).
--title TITLE page title
--jquery JQUERY Web path to JQuery library Example: `http://gallery.dece.space/#http://unrelated.host/`
--masonry MASONRY Web path to Masonry library
--imagesloaded IMAGESLOADED This method lets you host only one copy of the gallery page and provide
Web path to ImagesLoaded library different links for each gallery. One drawback is that the server hosting your
--lightbox LIGHTBOX Web path to Lightbox2 library images must have its CORS policy configured to let your browser load the photos.
--lightbox-css LIGHTBOX_CSS
Web path to Lightbox2 CSS ### 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…

View file

@ -1,171 +1,62 @@
"""An elementary Web gallery generator using Masonry and Lightbox.""" """An elementary Web gallery generator using Masonry and Lightbox."""
import argparse
import json import json
import os import os
from argparse import ArgumentParser
from PIL import Image from PIL import Image
SUPPORTED_TYPES = ("jpg", "jpeg", "png", "gif", "webp") 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 = """\ def main():
<!DOCTYPE html> ap = ArgumentParser(description="Generate JSON metadata from images.")
<html> ap.add_argument("dir", help="directory with the images to use")
<head> ap.add_argument("--no-thumbnails", action="store_true", help="disable thumbnail generation")
<meta charset="utf-8"> args = ap.parse_args()
<title>{title}</title> gen_gallery(args.dir, not bool(args.no_thumbnails))
<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 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 = [] entries = []
for filename in os.listdir(dirpath): for file_name in sorted(os.listdir(dir_path)):
ext = os.path.splitext(filename)[1][1:].lower() name, ext = os.path.splitext(file_name)
if ext not in SUPPORTED_TYPES: if name.endswith("_t"): # do not add already generated thumbnails
continue continue
image = Image.open(os.path.join(dirpath, filename)) if ext[1:].lower() not in SUPPORTED_TYPES:
continue
image = Image.open(os.path.join(dir_path, file_name))
dimensions = image.size dimensions = image.size
entries.append({ entry = {
"src": filename, "src": file_name,
"w": dimensions[0], "w": dimensions[0],
"h": dimensions[1], "h": dimensions[1],
"title": "", "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: 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) json.dump(entries, data_file)
except OSError as exc: except OSError as exc:
exit(f"Can't write data.json file: {exc}") exit(f"Can't write data.json file: {exc}")
print("Data JSON saved.") 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 gen_thumbnail(image: Image.Image, file_name: str, dir_path: str) -> str:
def main(): """Generate a thumbnail from this image, return its name."""
ap = argparse.ArgumentParser() thumbnail = image.copy()
ap.add_argument("dir", help="directory with the photos") thumbnail.thumbnail((360, 720), resample=Image.Resampling.LANCZOS)
ap.add_argument("--title", default="EmlGallery", help="page title") name, ext = os.path.splitext(file_name)
ap.add_argument("--jquery", help="Web path to JQuery library") thumbnail_file_name = name + "_t" + ext
ap.add_argument("--masonry", help="Web path to Masonry library") thumbnail_path = os.path.join(dir_path, thumbnail_file_name)
ap.add_argument("--imagesloaded", help="Web path to ImagesLoaded library") thumbnail.save(thumbnail_path)
ap.add_argument("--lightbox", help="Web path to Lightbox2 library") return thumbnail_file_name
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)
if __name__ == "__main__": if __name__ == "__main__":

105
index.html Normal file
View file

@ -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>

View file

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