From da48104702585675d8738d5b700004bf39b9ec35 Mon Sep 17 00:00:00 2001 From: dece Date: Tue, 17 May 2022 18:25:36 +0200 Subject: [PATCH] init --- .gitignore | 3 + README.md | 46 ++++++ emlg/__main__.py | 172 +++++++++++++++++++++++ emlg/__pycache__/__main__.cpython-39.pyc | Bin 0 -> 4713 bytes gallery-gen.go | 79 +++++++++++ pyproject.toml | 3 + setup.cfg | 22 +++ 7 files changed, 325 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 emlg/__main__.py create mode 100644 emlg/__pycache__/__main__.cpython-39.pyc create mode 100644 gallery-gen.go create mode 100644 pyproject.toml create mode 100644 setup.cfg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25aacff --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +*.egg-info/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f8cc08 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +EmlGallery +========== + +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. + +The Go source is a prototype I did a few years ago and kept here because it's +fun to me that I used Go at some point! + +License WTFPLv2. + + + +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. + +```bash +emlg "/home/dece/Photos/2022 dubious trip to antartica" +ls $! +# → data.json index.html 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. + +``` +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 +``` diff --git a/emlg/__main__.py b/emlg/__main__.py new file mode 100644 index 0000000..7d9b2b4 --- /dev/null +++ b/emlg/__main__.py @@ -0,0 +1,172 @@ +"""An elementary Web gallery generator using Masonry and Lightbox.""" +import argparse +import json +import os + +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 gen_gallery(dirpath: str, title: str, libs: dict): + entries = [] + for filename in os.listdir(dirpath): + ext = os.path.splitext(filename)[1][1:].lower() + if ext not in SUPPORTED_TYPES: + continue + image = Image.open(os.path.join(dirpath, filename)) + dimensions = image.size + entries.append({ + "src": filename, + "w": dimensions[0], + "h": dimensions[1], + "title": "", + }) + + try: + with open(os.path.join(dirpath, "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) + + +if __name__ == "__main__": + main() diff --git a/emlg/__pycache__/__main__.cpython-39.pyc b/emlg/__pycache__/__main__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08ad4b538066c10b6ff46e31766b63a235c27dd9 GIT binary patch literal 4713 zcmb6c%W~Yt5wCp^e2P+~B1KA#7}~olY8RxWSPDfdrb$MRqD0x0OBO6;3)~s*VuhE0 zSw2>~xyZ>Wc6>-x4pFMeDqr#g`4}9NlP~#z#D{bbfaOxMRSr;?!SwX>Yr1=StnqPO zg6H4g^|pUCElK~v!SJKO;6wPLeQ2m;N>rwbBb&156;lztYO2tyj^^gfoGekz%+s7% zpn0=M3+4zdnmQdZN2zX((NS}pj+ql|65c5~{#vCIbn*+$JVmGIsV^jRnw_T8&y|p~ z=~oiaQnsYZ>F%#rJi;92GLKu~0eQ@}Nz-y12LDawvC!gvNIH@2HOYM|@;!iC9wi%g zv&Fal{o1!6zVx6X$GN+%)nt{NskVcrskH;vG}XX^rfKh(+8)~uy5|hX-j0m+iw@9F zyP9izwRY6Kn9R8e6C>?w!Sg1~P~Ja@Vk5)I@h!^e3A~vE&q-yxVzeWGqynexX5YkV zL~kGg!1|3MNlO>kHdh}#dALqm+;wj0OIgP(dP^tpEV+zZq|vg%i1Fo_4&SNXm`SI& z%^h~@rRe%gMlu{)}h@mx=A!+_s!* z)UX`3yZ}5BN)bN*Ci+Vdxt~Cc6qJlER;#4_F&dpztEpg7BeVlfqOh?%^D^0y{>-f< zBY~$#xG>xH_lelyXhRtM2MDFx_BN#x?1p#lZB%5M`PY?WK921@C#{g}fV|mm)=}db z5;A9bMra(h7~`4kPw>o^4D-Xy)0d2v*vX}BpB|)oQ+t;*94m^JXHty@wl-jHV0U3d zFns&i?kQ-=NXP+o;WgGha3uthmts%0t;X|a=yyE2NVXjq+|>KJafw_54{$oJ7m@7) zLfMYhaX7hT=(SXibh&GVP1{=}R)_mH1wr8=i)7*2_4!~QiDfIQHfJue-R6m<)oq`1 z--QJN9W9YkxcWg#0HnD(kjD2&&Zz~F2Q`Ml5)pIxS(cn9+pdx(Mp#S@-_HJ z{SX5mp@zsK@1kv;q>T6p=_GO!QiPN+7nK%|Ib9y`L6j1;xK+)v4&as2bG7t+c;Z|b z5EG+gwEnQ7i8w*PJX%Jr-_j3H5X*+6`<(`aEOt`O!Lph~=_=;Faw*L@!dbJuM%`wI zkW)!etYJ}vx74h3c7UI1(ogmzK}xxlkIjo2YYVE91{5D)xXj%WIgag;WXi{QP%aWk zTgNla4(AM(E~GHDpxB*+V{-l^2U)pN3qrpcGElf&!2mibRmrgq{0RGH=rQa)OrGBoL zRp?28ryR-sd@tWF_6t4raHOa83KUjAi|bXYy#`vc^kSmB#s}+Y?nt5eSIVEx_lsOV z9PO#S;*latkD*f5;C-dak~g&=Mc>RrC5!J9_#R8%6;0w}2mh3%{)jAf&+&0u=#BhU z`kV3!csvFy3NybO@${shm*yotL5n!s{cMQ;NXoy~*L(WmB%f-Z+L3xPpXR5j{#xxR z-^gFdDEA48rWd5I<>BEPcpFCP$t7A#M6+%@c#hMT4EY{k-4K1GYaX!f$00YO4HQI55xUjjk9)>;; zW&1Xdb3tf(&M!oJrLax_q!9hFU4ByG)BjVMsTGZ8o|Xf8y%{^OYX^SE$Hza^!eZmUVy$B z{E?49uLpnTqtK59uOTd_U+4M?pMdcsd{aFILs~fd=?Ae2p3^-GjW_@fp!$Kz1~MWE zT0Zw9xNK@I<^DS{RWGZ=zZqzQ}aNrxCg2ly*QaqOZ;l-(PDlWn`BEV~_Ik6Hp zMWyu+hoLzJRX`nP1l#+oe2s(B5 z{>tt3t&PoIu{WPgFbh(IGjFik+HObp(lQUsWI|C=v_R{truy*ihDcN4B*LoL)UYwIna5@Un{(Kl$L0bwkfS+dDrz^lh}8i=)5lOF pG$<~)KJCCw9us|pAT5zs8B;VxgR~%CMZ2h7(B`IpUOZEj{|B$_J`4Z= literal 0 HcmV?d00001 diff --git a/gallery-gen.go b/gallery-gen.go new file mode 100644 index 0000000..219e58e --- /dev/null +++ b/gallery-gen.go @@ -0,0 +1,79 @@ +package main + +import ( + "bufio" + "encoding/json" + "image" + _ "image/jpeg" + _ "image/png" + "log" + "os" + "path/filepath" +) + +// FileEntry contains informations for PowerSlide. +type FileEntry struct { + Name string `json:"src"` + Width int `json:"w"` + Height int `json:"h"` + Caption string `json:"title"` +} + +func main() { + dir, err := os.Getwd() + if err != nil { + panic(err) + } + + entries := buildEntries(dir) + + encodedJSON, err := json.MarshalIndent(entries, "", " ") + if err != nil { + panic(err) + } + + outputFile, err := os.Create(filepath.Join(dir, "data.json")) + if err != nil { + panic(err) + } + defer outputFile.Close() + + outputWriter := bufio.NewWriter(outputFile) + _, err = outputWriter.Write(encodedJSON) + if err != nil { + panic(err) + } + outputWriter.Flush() +} + +func buildEntries(root string) []FileEntry { + var entries []FileEntry + filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + ext := filepath.Ext(path) + if !(ext == ".png" || ext == ".jpg" || ext == ".gif") { + return nil + } + + width, height := getImageDimensions(path) + entries = append(entries, FileEntry{info.Name(), width, height, ""}) + return nil + }) + return entries +} + +func getImageDimensions(path string) (int, int) { + file, err := os.Open(path) + if err != nil { + log.Println(err) + return 0, 0 + } + defer file.Close() + + config, _, err := image.DecodeConfig(file) + if err != nil { + log.Println(err) + return 0, 0 + } + + return config.Width, config.Height +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9787c3b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..44c424c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,22 @@ +[metadata] +name = emlg +version = 0.0.1 +description = Minimal HTML/JS photo gallery +long_description = file: README.md +long_description_content_type = text/markdown +license = GPLv3 +author = dece +author-email = shgck@pistache.land +home-page = https://git.dece.space/Dece/EmlGallery +classifiers = + Environment :: Console + Programming Language :: Python :: 3 + +[options] +packages = emlg +python_requires = >= 3.7 +setup_requires = setuptools >= 38.3.0 + +[options.entry_points] +console_scripts = + emlg = emlg.__main__:main