diff --git a/.gitignore b/.gitignore index 25aacff..4e7a394 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build/ dist/ *.egg-info/ +__pycache__/ diff --git a/README.md b/README.md index 7f8cc08..c633117 100644 --- a/README.md +++ b/README.md @@ -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… diff --git a/emlg/__main__.py b/emlg/__main__.py index 7d9b2b4..ee608d2 100644 --- a/emlg/__main__.py +++ b/emlg/__main__.py @@ -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 = """\ - - - - - {title} - - - - - - - - - - - - - - -
- -
- - - - -""" - -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__": diff --git a/emlg/__pycache__/__main__.cpython-39.pyc b/emlg/__pycache__/__main__.cpython-39.pyc deleted file mode 100644 index 08ad4b5..0000000 Binary files a/emlg/__pycache__/__main__.cpython-39.pyc and /dev/null differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..f2750e0 --- /dev/null +++ b/index.html @@ -0,0 +1,105 @@ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + diff --git a/setup.cfg b/setup.cfg index 44c424c..194dd05 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 =