houston.family

how-to

houston.family presents…

make a photo gallery

Overview

The goal of this page is to show how to create a web gallery for the photos scanned with the Epson FastFoto scanner. The gallery should show the photos in chronological order just like the photo albums. However, because photos are filed by year and month, that in turn becomes alphabetically sorted by computers, thus losing their chronological sorting.

We'll organize them by album and then use a script to sort them by their prefixes.

Beginning with FastFoto

When you scan the photos, it's much easier if you can assign the month and year to them. From there, FastFoto organizes the scans into folders based on the date. Additionally, the photos will contain date information known as 'exif' data.

Once you have scanned photos, I prefer to upload them to the web server. That way all the steps below can be performed on the server and only that initial upload is necessary.

I use rsync to copy the folder contents onto the web. Here's an example of an rsync command that will mirror the 'FastFoto' album onto your server.

rsync -av --progress /home/username/Documents/FastFoto/ houston.family:/home/username/public_html/houston.family/FastFoto/

One Album at a Time

Copy the folders containing all of the album's pictures into a separate folder. In my case, I'm copying the following folders into 'album21':

  • 1997_August_
  • 1997_December_
  • 1997_July_
  • 1997_November_
  • 1997_October_
  • 1997_September_

All of the files within have unique filenames given that their prefixes match the folder they're in. Knowing that, it's safe to move all the nested files into the root folder. At the same time, we will discard the version of the photos that has not been enhanced. Don't worry, they are safe in the source folder 'FastFoto'.

protip: the highlighted words have a dropdown triangle for more content

• create python script move.py in folder 'album21'

• copy the contents of that to your clipboard

move.py
#!/usr/bin/env python3

from pathlib import Path
import shutil

root = Path(".")

# 1. Move enhanced photos to root
for file in root.rglob("*_a.jpg"):
	if file.parent == root:
		continue

	destination = root / file.name

	if destination.exists():
		print(f"SKIPPED duplicate: {file} -> {destination}", flush=True)
		continue

	shutil.move(str(file), str(destination))
	print(f"Moved: {file} -> {destination}", flush=True)

# 2. Delete unenhanced jpg files
for file in root.rglob("*.jpg"):
	if file.parent == root and file.name.endswith("_a.jpg"):
		continue

	file.unlink()
	print(f"Deleted: {file}", flush=True)

# 3. Remove empty folders, deepest first
for folder in sorted(root.rglob("*"), key=lambda p: len(p.parts), reverse=True):
	if folder.is_dir():
		try:
			folder.rmdir()
			print(f"Removed empty folder: {folder}", flush=True)
		except OSError:
			pass
cat > move.py

Type that, paste clipboard contents ctrl + v, and then ctrl + d (may have to hit ctrl + d twice)

• run move.py

• create 'img' and 'og' folders and move all photos into it.

mkdir -p og && mv *.jpg og/ && mkdir -p img && mv og/ img/

Resize Photos

Now we will create various sizes of all the photos. This is so photos can load quickly on the web and to give some options for when they're saved to devices. These are my standard sizes based on the longest dimension:

  • sm: 480
  • md: 960
  • lg: 1920

• create and run resize.py

resize.py
#!/usr/bin/env python3

import os
from PIL import Image, ImageOps

SRC_DIR = "og"

SIZES = {
    "sm": 480,
    "md": 960,
    "lg": 1920
}

EXTS = (".jpg", ".jpeg", ".png", ".webp", ".tif", ".tiff")

def resize_image(img, target_size):
    w, h = img.size

    if h > w:
        # Portrait: resize by height
        ratio = target_size / h
        new_width = int(w * ratio)
        new_height = target_size
    else:
        # Landscape or square: resize by width
        ratio = target_size / w
        new_width = target_size
        new_height = int(h * ratio)

    return img.resize((new_width, new_height), Image.LANCZOS)

def main():
    if not os.path.isdir(SRC_DIR):
        print("Missing 'og' folder")
        return

    for size in SIZES:
        os.makedirs(size, exist_ok=True)

    for filename in os.listdir(SRC_DIR):
        if not filename.lower().endswith(EXTS):
            continue

        src_path = os.path.join(SRC_DIR, filename)

        try:
            with Image.open(src_path) as img:
				# Apply Finder/Mac orientation before resizing
                img = ImageOps.exif_transpose(img)
                img = img.convert("RGB")

                for folder, target_size in SIZES.items():
                    resized = resize_image(img, target_size)
                    out_path = os.path.join(folder, filename)
                    resized.save(out_path, quality=90, optimize=True)

                print(f"Processed: {filename}")

        except Exception as e:
            print(f"Error with {filename}: {e}")

if __name__ == "__main__":
    main()

Sort by Prefixes

We'll create a file that indexes the prefixes of the photos. Then when we generate the gallery this index will be used to sort the photos in the right order.

• create and run prefixes.py

prefixes.py
import os
import re

directory = "."  # Top-level directory
prefixes = set()

# Match pattern: 1982_June_0001.jpg or 1982_June_0001_a.jpg etc.
pattern = re.compile(r"^(.*?_)\d{4}(?:_[a-z])?\.jpg$", re.IGNORECASE)

for root, _, files in os.walk(directory):
	for filename in files:
		match = pattern.match(filename)
		if match:
			prefixes.add(match.group(1))

# Write to prefixes.txt
with open("prefixes.txt", "w") as f:
	for prefix in sorted(prefixes):
		f.write(prefix + "\n")

print(f"Found {len(prefixes)} unique prefixes. Output saved to prefixes.txt.")

Running prefixes.py will generate a file called prefixes.txt

• copy / paste prefixes.txt into a spreadsheet app

You can view it in the terminal by typing the following:

cat prefixes.txt

• add index numbers and sort

• copy / paste back into prefixes.txt. see below

prefixes.txt
1	1997_July_
2	1997_August_
3	1997_September_
4	1997_October_
5	1997_November_
6	1997_December_

• run tab delimited to comma delimited csv shell command

sed 's/\t/,/g' prefixes.txt > prefixes.csv

Files for Downloading

I package the 1920 px-sized photos in a zipped folder called album21-sm.zip. And then I package the original-sized photos in a zipped folder called album21-lg.zip. These sizes aren't to be confused with the intended-for-web sized photos 😆😆😆.

From the terminal, cd into the appropriate folder, 'lg' for example. Then:

zip -r ../../files/album21-sm.zip .

Check the size of the files

ls -lh

Send It

Prepare the index.html then push it to the folder

rsync -av --progress /home/username/Documents/Websites/houston.family/21/dist/index.html \
houston.family:/home/username/public_html/houston.family/login/albums/21/

Cleanup

MacOS creates hidden files. These slow down uploading, syncing, and zipping times. Remove them with this command.

find static/img \( -name '._*' -o -name '.DS_Store' \) -type f -delete

All the file generating tends to create files without proper permissions. So from the root of the albums folder, run this command.

find . -type f ! -perm 644 -exec chmod 644 {} +
find . -type d ! -perm 755 -exec chmod 755 {} +

Sometimes I need to count files

find . -type f | wc -l