summaryrefslogtreecommitdiff
path: root/build.py
diff options
context:
space:
mode:
Diffstat (limited to 'build.py')
-rwxr-xr-xbuild.py245
1 files changed, 245 insertions, 0 deletions
diff --git a/build.py b/build.py
new file mode 100755
index 0000000..f018b94
--- /dev/null
+++ b/build.py
@@ -0,0 +1,245 @@
+#!/usr/bin/env python3
+import jinja2
+import PIL.Image
+import PIL.ImageOps
+import argparse
+import yaml
+import os
+import re
+import hashlib
+import glob
+from datetime import datetime
+
+imagereg = re.compile("([a-z0-9]*)\\..*")
+
+
+def hashfile(fname):
+ md5 = hashlib.md5()
+ print(fname)
+ with open(fname, "rb") as f:
+ for chunk in iter(lambda: f.read(4096), b""):
+ md5.update(chunk)
+
+ return md5
+
+
+def parse_res(res):
+ return int(res)
+
+
+class FileLoader(jinja2.BaseLoader):
+ def get_source(self, environment, template):
+ if not os.path.exists(template):
+ raise jinja2.TemplateNotFound(template)
+ mtime = os.path.getmtime(template)
+ with open(template, "r") as f:
+ source = f.read()
+ return source, template, lambda: mtime == os.path.getmtime(template)
+
+
+class ImageLocation:
+ def __init__(self, folder):
+ self.folder = folder
+ self.stuff = {}
+
+ self.unused = set()
+ self.existing = {}
+
+ if not os.path.isdir(folder):
+ os.mkdir(folder)
+
+ self.look()
+
+ self.tmpname = os.path.join(folder, "tmp")
+
+ def look(self):
+ for name in os.listdir(self.folder):
+ m = imagereg.match(name)
+ if m is not None:
+ fhash = m.group(1)
+
+ self.unused.add(fhash)
+ self.existing[fhash] = name
+
+ def convert(self, imgname, settings):
+ # First determine the hash from settings
+ thash = hashlib.md5(
+ bytes(
+ f"{settings['res']}+{settings['ext']}" + imgname,
+ encoding="utf8")
+ ).hexdigest()
+
+ # Now check if it exists
+ if thash in self.existing:
+ print(f"Skipping file {imgname}[{thash}]")
+ return self.existing[thash], thash
+
+ # Okay convert it
+ fname = f"{thash}.{settings['ext']}"
+ print(f"Converting file {imgname} to {fname}")
+
+ tmpname = f"{self.tmpname}.{settings['ext']}"
+ with PIL.Image.open(imgname) as im:
+ target_height = settings["res"]
+ im = PIL.ImageOps.exif_transpose(im)
+ if im.size[0] > target_height:
+ res = (int((im.size[0] / im.size[1]) * target_height), target_height)
+ im = im.resize(res, PIL.Image.ANTIALIAS)
+
+ im.save(tmpname)
+
+ os.rename(tmpname, os.path.join(self.folder, fname))
+
+ self.existing[thash] = fname
+ self.unused.add(thash)
+
+ return fname, thash
+
+ def fetch(self, imgname, settings, relto=None):
+ # Check if we already have it
+ fname, hash = self.convert(imgname, settings)
+
+ # Mark the hash as used
+ self.unused.remove(hash)
+
+ if relto is not None:
+ full = os.path.join(self.folder, fname)
+ return os.path.relpath(full, start=relto)
+
+ return fname
+
+ def clean(self):
+ print("Running cleanup")
+ for uhash in self.unused:
+ fname = os.path.join(self.folder, self.existing[uhash])
+ print(f"Removing file {fname}")
+ os.remove(fname)
+
+class Image:
+ def __init__(self, filename):
+ self.name = os.path.basename(filename)
+ self.filename = filename
+ self.description = ""
+ self.scaledpath = ""
+ self.fullurl = ""
+
+ self.taken = None
+ self.load_metadata()
+
+ def load_metadata(self):
+ try:
+ with PIL.Image.open(self.filename) as im:
+ taken_str = im.getexif()[36867]
+ except KeyError:
+ print(f"Could not load date from image {self.filename}")
+ return
+
+ self.taken = datetime.strptime(taken_str, "%Y:%m:%d %H:%M:%S")
+
+
+def value_or_default(val, default=None):
+ if val is None:
+ return default
+ return val
+
+
+class Loader:
+ def __init__(self, config_file, loadpath):
+ self.config = Loader.__load_config(config_file)
+
+ self.loadpath = loadpath
+
+ self.images = []
+
+ def __load_config(config_file):
+ config = {}
+ with open(config_file, "r") as f:
+ config = yaml.safe_load(f)
+
+ print(f"Loaded config: {config}")
+ return config
+
+ def load(self, imagehook=None, clear=False):
+ # Clear if neccesary
+ if clear:
+ self.images = []
+
+ # First find all images from glob
+ for image in glob.iglob(os.path.join(self.loadpath, self.config["imageglob"])):
+ img = Image(image)
+ img.description = self.config["info"].get(img.name, "")
+
+ if imagehook is not None:
+ img = imagehook(img)
+
+ self.images.append(img)
+
+ self.images = sorted(self.images,
+ key=lambda img: value_or_default(img.taken,
+ datetime.min),
+ reverse=True)
+
+
+class Renderer:
+ def __init__(self, loader, resolution, extension):
+ self.loader = loader
+ self.scaled = None
+
+ self.settings = {
+ "res": parse_res(resolution),
+ "ext": extension,
+ }
+
+ def build_to(self, dest, template, context, loc=None, clean=False):
+ # Load images
+ if loc is None:
+ loc = ImageLocation(os.path.join(dest, "imgs"))
+
+ def imgproc(img):
+ if context["cgit"] is not None:
+ img.fullurl = os.path.join(context["cgit"], "plain", img.name)
+
+ img.scaledpath = loc.fetch(img.filename, self.settings, relto=dest)
+
+ return img
+
+ self.loader.load(imgproc, clear=True)
+
+ jenv = jinja2.Environment(
+ loader=FileLoader(),
+ autoescape=jinja2.select_autoescape(['html', 'xml'])
+ )
+
+ tmpl = jenv.get_template(template)
+
+ with open(os.path.join(dest, "index.html"), "w") as f:
+ f.write(tmpl.render({
+ "ctx": context,
+ "images": self.loader.images
+ }))
+
+ if clean:
+ loc.clean()
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("--dest", "-d", default="build", help="where to put resulting files")
+parser.add_argument("--size", "-s", default="1080", help="size to scale web images to")
+parser.add_argument("--commit", "-g", help="git commit hash to announce")
+parser.add_argument("--clean", help="clean unused image files", action="store_true")
+parser.add_argument("--config", "-c", default="imginfo.yml", help="where to load image definitions from")
+parser.add_argument("--template", "-t", default="index.html.j2", help="html template to use")
+parser.add_argument("--cgit", "-w", help="cgit repo base url")
+parser.add_argument("--load", "-l", default=".", help="where to load full size images from")
+parser.add_argument("--ext", "-e", default="png", help="image extension to use")
+
+args = parser.parse_args()
+
+context = {
+ "cgit": args.cgit,
+ "git": args.commit
+ }
+
+loader = Loader(args.config, args.load)
+rend = Renderer(loader, args.size, args.ext)
+rend.build_to(args.dest, args.template, context, clean=args.clean)