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.
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.
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:
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: