123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- #!/usr/bin/env python3
-
- import os
- import shutil
- import re
- import yaml
-
- from operator import itemgetter
-
- from jinja2 import Environment, PackageLoader, select_autoescape
- from PIL import Image, ExifTags
- from dotenv import load_dotenv
-
- from configuration import SITE_TITLE, SITE_AUTHOR, SITE_AUTHOR_WEBSITE, THEME
-
-
- # Load env var from .env file
- load_dotenv(dotenv_path=".env")
-
-
- PICTURES_DIR_NAME = "photos"
- OUTPUT_DIR_NAME = "output"
-
- # Configure YAML to accept environment variables in metadata files
- # Based on https://stackoverflow.com/a/27232341 solution
- pattern = re.compile(r"^ENV\[\'(.*)\'\]$")
- yaml.add_implicit_resolver("!pathex", pattern)
-
-
- def pathex_constructor(loader, node):
- value = loader.construct_scalar(node)
- env_var, = pattern.match(value).groups()
- return os.getenv(env_var)
-
-
- yaml.add_constructor("!pathex", pathex_constructor)
-
-
- def list_galleries_in(path):
- gallery_dirs = [f for f in os.scandir(path) if f.is_dir()]
- for gallery_dir in gallery_dirs:
- metadata = {}
- metadata_path = os.path.join(gallery_dir.path, "metadata.yml")
- if os.path.exists(metadata_path):
- with open(metadata_path, "r") as metadata_file:
- metadata = yaml.load(metadata_file)
-
- private = False
- url = f"{gallery_dir.name}.html"
- output_path = gallery_dir.name
- password = metadata.get("password", None)
- if password:
- private = True
- url = f"{gallery_dir.name}-{password}.html"
- output_path = f"{output_path}-{password}"
-
- photos = list(list_photos_in(gallery_dir, output_path))
- if len(photos) == 0:
- continue
- photos.sort(key=itemgetter("name"))
-
- # Try to get cover from metadata, if it doesn't exist, take one by
- # default.
- cover_photo = None
- cover_name = metadata.get("cover")
- if cover_name:
- cover_photo = find_photo(photos, cover_name)
- if cover_photo is None:
- cover_index = (len(gallery_dir.name) + 42) % len(photos)
- cover_photo = photos[cover_index]
-
- gallery = {
- "name": metadata.get("name", gallery_dir.name),
- "path": gallery_dir.path,
- "output_path": output_path,
- "url": url,
- "num_photos": len(photos),
- "photos": photos,
- "cover_photo": cover_photo,
- "private": private,
- }
- yield gallery
-
-
- def find_photo(photos, photo_name):
- for photo in photos:
- if photo["name"] == photo_name:
- return photo
- return None
-
-
- def list_photos_in(gallery_dir, gallery_output_path):
- photo_files = [
- f for f in os.scandir(gallery_dir) if re.match(".+\.jpg", f.name, re.I)
- ]
- for photo_file in photo_files:
- url = os.path.join(gallery_output_path, photo_file.name)
- thumb_url = os.path.join(gallery_output_path, f"thumb_{photo_file.name}")
- photo = {
- "name": photo_file.name,
- "path": photo_file.path,
- "url": url,
- "thumb_url": thumb_url,
- }
- yield photo
-
-
- def generate_output_dir():
- output_path = os.path.join(os.curdir, OUTPUT_DIR_NAME)
- if not os.path.isdir(output_path):
- os.mkdir(output_path)
- return output_path
-
-
- def generate_style(output_path):
- style_path = os.path.join(os.curdir, THEME, "style")
- style_output_path = os.path.join(output_path, "style")
- if os.path.isdir(style_output_path):
- shutil.rmtree(style_output_path)
- shutil.copytree(style_path, style_output_path)
-
-
- def generate_index(output_path, galleries):
- index_path = os.path.join(output_path, "index.html")
-
- theme_path = os.path.join(os.curdir, THEME)
- jinja_env = Environment(
- loader=PackageLoader("boop", theme_path), autoescape=select_autoescape(["html"])
- )
- index_template = jinja_env.get_template("index.html.j2")
-
- with open(index_path, "w") as index_file:
- index_file.write(
- index_template.render(
- galleries=galleries,
- site_title=SITE_TITLE,
- site_author=SITE_AUTHOR,
- site_author_website=SITE_AUTHOR_WEBSITE,
- )
- )
-
-
- def generate_gallery(output_path, gallery):
- generate_gallery_index(output_path, gallery)
- generate_gallery_photos(output_path, gallery)
-
-
- def generate_gallery_index(output_path, gallery):
- gallery_index_path = os.path.join(output_path, gallery["url"])
-
- theme_path = os.path.join(os.curdir, THEME)
- jinja_env = Environment(
- loader=PackageLoader("boop", theme_path), autoescape=select_autoescape(["html"])
- )
- gallery_template = jinja_env.get_template("gallery.html.j2")
-
- with open(gallery_index_path, "w") as gallery_file:
- gallery_file.write(
- gallery_template.render(
- gallery=gallery,
- site_title=SITE_TITLE,
- site_author=SITE_AUTHOR,
- site_author_website=SITE_AUTHOR_WEBSITE,
- )
- )
-
-
- def generate_gallery_photos(output_path, gallery):
- gallery_output_path = os.path.join(output_path, gallery["output_path"])
- if not os.path.isdir(gallery_output_path):
- os.mkdir(gallery_output_path)
-
- for photo in gallery["photos"]:
- photo_output_path = os.path.join(output_path, photo["url"])
- if not os.path.exists(photo_output_path):
- shutil.copyfile(photo["path"], photo_output_path)
-
- thumb_output_path = os.path.join(output_path, photo["thumb_url"])
- if not os.path.exists(thumb_output_path):
- generate_thumb_file(thumb_output_path, photo)
-
-
- def generate_thumb_file(output_path, photo):
- orientation_key = get_orientation_exif_key()
- size = (440, 264)
-
- with Image.open(photo["path"]) as image:
- # First, make sure image is correctly oriented
- exif = image._getexif()
- if exif[orientation_key] == 3:
- image = image.rotate(180, expand=True)
- elif exif[orientation_key] == 6:
- image = image.rotate(270, expand=True)
- elif exif[orientation_key] == 8:
- image = image.rotate(90, expand=True)
-
- w, h = image.size
- if w > size[0] and h > size[1]:
- # If the original file is larger in width AND height, we resize
- # first the image to the lowest size accepted (both width and
- # height stays greater or equal to requested size).
- # E.g. 1200x900 is resized to 440x330
- # 1200x600 is resized to 528x264
- if size[0] / size[1] <= w / h:
- w = int(max(size[1] * w / h, 1))
- h = 264
- else:
- h = int(max(size[0] * h / w, 1))
- w = 440
- new_size = (w, h)
-
- image.draft(None, new_size)
- image = image.resize(new_size, Image.BICUBIC)
-
- # We now have an image with at least w = 440 OR h = 264 (unless one of
- # the size is smaller). But the image can still be larger than
- # requested size, so we have to crop the image in the middle.
- crop_box = None
- if w > size[0]:
- left = (w - size[0]) / 2
- right = left + size[0]
- crop_box = (left, 0, right, h)
- elif h > size[1]:
- upper = (h - size[1]) / 2
- lower = upper + size[1]
- crop_box = (0, upper, w, lower)
- if crop_box is not None:
- image = image.crop(crop_box)
-
- # And we save the final image.
- image.save(output_path)
-
-
- def get_orientation_exif_key():
- for (key, tag) in ExifTags.TAGS.items():
- if tag == "Orientation":
- return key
-
-
- def main():
- print("Loading galleries... ", end="")
- pictures_folder = os.path.join(os.curdir, PICTURES_DIR_NAME)
- galleries = list(list_galleries_in(pictures_folder))
- if len(galleries) == 0:
- print(f"No galleries found in {pictures_folder}")
- return
- galleries.sort(key=itemgetter("name"))
- print(f"{len(galleries)} galleries found.")
-
- print("Generating output folder... ", end="")
- output_path = generate_output_dir()
- print("✔️")
- print("Generating style folder... ", end="")
- generate_style(output_path)
- print("✔️")
- print("Generating index file... ", end="")
- generate_index(output_path, galleries)
- print("✔️")
- for gallery in galleries:
- print(f"Generating {gallery['name']} gallery ({gallery['url']})... ", end="")
- generate_gallery(output_path, gallery)
- print("✔️")
- print("Galleries generated 🎉")
-
-
- if __name__ == "__main__":
- main()
|