A fast, incremental static site generator for family photo albums, written in Delphi.
Built as a replacement for a Hugo-based photo site that had grown to 26,000 files and multi-hour build times. The Hugo static site generator is great for small to medium sized informational sites but for this particular theme, it creates 7 copies of every image in the photo gallery, supposedly to support different resolutions. But with several years worth of photos of our family and thousands of pictures, it was just taking too long to regenerate the files and pages--and it would regenerate every single page! This Delphi program updates a site in a fraction of the time and uses a quarter of the space for the resulting website.
Note: Except for this and the previous paragraph, the entire application and this README were written by Claude Code with very few corrections (used
publishedinstead ofpublicin project-level classes; didn't useCharInSetfor checking characters in a set; removeinlinein a couple of places).
- Incremental builds — a JSON manifest tracks source modification times; only albums whose photos have changed are rebuilt
- Fast image processing — generates exactly two output images per photo (a resized thumbnail and a full-size copy), compared to the seven variants Hugo's image pipeline produced
- WebStencils templates — uses the Delphi 13.1 Florence template engine for HTML generation; templates are plain HTML files with
@expressions - Masonry column layout — photos and album cards are distributed across columns using a shortest-column greedy algorithm based on actual image pixel heights
- Taxonomy pages — automatically builds
/tags/and/locations/index pages from album metadata - Fully relative URLs — every
hrefandsrcin the generated HTML is relative, so the site can be deployed in a sub-folder of any domain - Hugo content compatibility — reads existing Hugo
_index.mdfront matter (TOML config + YAML content) without modification - Cross-platform goal — image processing uses Skia4Delphi, which targets both Windows and Linux
| Requirement | Notes |
|---|---|
| RAD Studio 13.1 Florence (Delphi 12.1+) | Uses WebStencils and Skia4Delphi, both bundled with Florence |
| VSoft.YAML | YAML front matter parsing; add to your library path |
| Hugo theme (autophugo or similar) | Provides the CSS, JS, and web fonts copied to the output |
PhotoAlbumBuilder expects a two-level Hugo content tree:
content/
_index.md ← site description
<category>/
_index.md ← category title, description, albumthumb, weight
<album>/
_index.md ← album title, description, albumthumb, tags, locations, resources
Category _index.md:
---
title: "Misc"
description: "Miscellaneous photos"
albumthumb: "gallery_icons/noun-misc-512.png"
weight: 10
draft: false
---Album _index.md:
---
title: "Cannon Beach 2010"
description: "A weekend trip to the Oregon coast."
albumthumb: "beach/cover.jpg"
date: 2010-08-14
weight: 0
draft: false
tags: ["vacation", "beach"]
locations: ["Oregon"]
resources:
- IMG_0001.jpg
- IMG_0002.jpg
- 100_3319.jpg
---Note: The
resourceslist is read with a hand-rolled parser rather than through the YAML library. This works around a VSoft.YAML 1.1 lexer bug that silently strips underscores from filenames that look like numeric literals (e.g.100_3319.jpg→1003319.jpg).
output/
index.html ← root category grid
404.html
tags/index.html
locations/index.html
assets/
css/ ← synced from theme\assets every build (copy-if-newer)
js/ ← synced from theme\assets every build (copy-if-newer)
fonts/ ← copied from theme\static on --force
icons/ ← category icon PNGs, copied on demand per category
<category>/
index.html ← album grid for this category
<album>/
index.html ← photo masonry page
thumbs/
<filename>.jpg ← resized thumbnails
<filename>.jpg ← full-size copies
PhotoAlbumBuilder [options]
Options:
--config <path> Path to config.toml (default: .\config.toml)
--content <path> Path to content root (default: .\content)
--assets <path> Path to photo assets (default: .\assets)
--theme <path> Hugo theme root (default: .\themes\autophugo)
--templates <path> HTML template root (default: .\templates)
--output <path> Output directory (default: .\output)
--force Rebuild all pages + force a full re-copy of static assets
--dry-run Parse and plan without writing any files
All default paths are resolved relative to the current working directory (the folder you run the program from), not the executable's location. Any path may be overridden with its flag, or via
config.tomlwhere supported.
Incremental rebuild after adding photos to one or more albums:
PhotoAlbumBuilder
Every build also syncs changed theme CSS/JS and copies any newly referenced
category icons, so a plain run keeps assets current without --force.
Force a full rebuild of every page and a complete asset re-copy:
PhotoAlbumBuilder --force
Use this after editing theme fonts, or any time you want to overwrite the
entire output/assets/ tree regardless of timestamps.
Check what would be built without touching any files:
PhotoAlbumBuilder --dry-run
PhotoAlbumBuilder reads a subset of Hugo's config.toml. Recognised keys:
title = "My Family Photos"
[params]
description = "A collection of family memories."
robots_tags = "noindex, nofollow"
thumb_width = 350 # thumbnail width in pixels (default 350)
thumb_quality = 75 # JPEG quality 1–100 (default 75)
column_count = 3 # masonry columns (default 3)
theme_path = "" # override theme root (optional)
[params.footer.paragraph]
headline = "About this site"
[params.footer.contact]
formspreeid = "your-id"
headline = "Get in touch"
buttontext = "Send"
resettext = "Reset"
[params.footer.copyright]
name = "Your Name"
[[params.header.links]]
name = "Tags"
url = "tags/"
icon = "fa-tags"
[[params.footer.social.links]]
label = "Instagram"
url = "https://instagram.com/yourhandle"
icon = "fa-instagram"Templates live in a templates/ folder under the current working directory by
default (override with --templates <path>).
| File | Purpose |
|---|---|
base.html |
Outer HTML shell; imports header/footer, renders CSS/JS links |
_header.html |
Site title, breadcrumb trail, nav links |
_footer.html |
Description, social links, taxonomy links, contact form |
cards.html |
Masonry grid of album or category cards (root and category pages) |
album.html |
Masonry grid of photos for a single album |
taxonomy.html |
Tags or Locations index with per-term card grids |
404.html |
Not-found page |
Templates use WebStencils syntax: @page.Title, @ForEach(var item in page.Items) { }, @if cond { }, @Import _partial.html, @LayoutPage "base.html".
Asset paths in templates use the @base variable, which is depth-adjusted so the site works in a sub-folder:
<link rel="stylesheet" href="@base.Assets/css/main.css">
<a href="@base.Root">Home</a>
<a href="@base.Tags/">All Tags</a>| Unit | Responsibility |
|---|---|
PhotoAlbum.Config |
Parses config.toml (hand-rolled TOML reader) and CLI arguments |
PhotoAlbum.Content |
Walks the content tree; parses YAML front matter via VSoft.YAML |
PhotoAlbum.Images |
Thumbnail generation and original copy via Skia4Delphi; EXIF rotation |
PhotoAlbum.Manifest |
Incremental build manifest (JSON); timestamp comparison with 1-second FAT tolerance |
PhotoAlbum.StencilData |
Data model classes exposed to WebStencils templates via RTTI |
PhotoAlbum.Columns |
Shortest-column greedy masonry balancer |
PhotoAlbum.Generator |
Orchestrates the build; renders templates; copies static assets |
The CSS, JavaScript, web fonts, and category icons are not generated — they come from your Hugo theme and photo assets, and are placed in output/assets/ so the site displays correctly. Each source is handled differently:
| Source | Destination | When |
|---|---|---|
{theme}/assets/ (whole tree, incl. css/, js/) |
output/assets/ |
Every build, copy-if-newer (or all files on --force) |
{theme}/static/fonts/ |
output/assets/fonts/ |
On --force only |
{assets}/gallery_icons/<icon> |
output/assets/icons/ |
On demand — only icons referenced by a category's albumthumb, copy-if-newer |
The whole {theme}/assets/ tree is mirrored, so adding files to the theme picks them up automatically on the next build without --force. Category icons are detected from content (like album photos are) rather than bulk-copied, so only icons actually used by a category are copied.
To clear out assets that are no longer referenced, delete
output/assets/and run a build — it will be re-synced from scratch.
MIT — see LICENSE for details.