diff --git a/src/stills/main.py b/src/stills/main.py
index e4555e4..9174151 100644
--- a/src/stills/main.py
+++ b/src/stills/main.py
@@ -19,12 +19,14 @@ def parse_args():
parser = argparse.ArgumentParser(
description='Generate a static HTML gallery from a folder of images.'
)
- parser.add_argument('--config', help='Path to a config file (TOML or JSON)', default=None)
+ parser.add_argument('--config', help='Path to a config file (TOML)', default=None)
parser.add_argument('--source', required=True, help='Source directory with images')
parser.add_argument('--output', required=True, help='Output directory for HTML and images')
parser.add_argument('--copyright', help='Copyright owner name')
parser.add_argument('--analytics-id', help='ID for analytics tracker')
parser.add_argument('--analytics-host', help='Analytics host')
+ parser.add_argument('--thumb-width', type=int, help='Thumbnail width in pixels')
+ parser.add_argument('--thumb-height', type=int, help='Thumbnail height in pixels')
return parser.parse_args()
def collect_images(source_dir: Path):
@@ -42,7 +44,7 @@ def extract_metadata(image_path: Path):
make = meta.get('Exif.Image.Make', None)
camera = meta.get('Exif.Image.Model', None)
- if make not in camera:
+ if make and camera and make not in camera:
camera = make + ' ' + camera
try:
film = xmp['Xmp.dc.description']['lang="x-default"']
@@ -57,14 +59,45 @@ def load_image_template() -> str:
with importlib.resources.files('stills.templates').joinpath('image.html').open('r', encoding='utf-8') as f:
return f.read()
+def generate_thumbnail(image_path: Path, thumb_path: Path, size=(300, 300)):
+ with Image.open(image_path) as img:
+ img = img.convert('RGB')
+ img_ratio = img.width / img.height
+ thumb_ratio = size[0] / size[1]
+
+ if img_ratio > thumb_ratio:
+ new_height = size[1]
+ new_width = int(new_height * img_ratio)
+ else:
+ new_width = size[0]
+ new_height = int(new_width / img_ratio)
+
+ img = img.resize((new_width, new_height), Image.LANCZOS)
+ left = (new_width - size[0]) // 2
+ top = (new_height - size[1]) // 2
+ img = img.crop((left, top, left + size[0], top + size[1]))
+
+ thumb_path.parent.mkdir(parents=True, exist_ok=True)
+ img.save(thumb_path, format='JPEG', quality=85)
+
def generate_html(images, output_dir: Path, author: str | None = None,
- analytics_id=None, analytics_host=None):
+ analytics_id=None, analytics_host=None,
+ thumb_size=(300, 300)):
template = load_template()
image_tags = []
+ thumb_css = (
+ f''
+ )
+
image_template = load_image_template()
photos_dir = output_dir / 'photos'
+ thumbs_dir = output_dir / 'thumbs'
photos_dir.mkdir(parents=True, exist_ok=True)
+ thumbs_dir.mkdir(parents=True, exist_ok=True)
analytics_html = ''
if analytics_host and analytics_id:
@@ -80,20 +113,18 @@ def generate_html(images, output_dir: Path, author: str | None = None,
)
for img in reversed(images):
- relative_image_path = f'photos/{img.name}'
- with Image.open(img) as im:
- width, height = im.size
-
- # Generate thumbnail HTML tag linking to individual page
html_page = img.with_suffix('.html').name
+ thumb_path = thumbs_dir / img.with_suffix('.jpg').name
+ relative_thumb_path = f'thumbs/{thumb_path.name}'
+ generate_thumbnail(img, thumb_path, thumb_size)
+
image_tags.append(
- f'\t'
- f''
+ f' '
+ f'
'
f''
)
camera_model, film = extract_metadata(img)
-
metadata_html = ''
if camera_model or film:
parts = []
@@ -103,18 +134,21 @@ def generate_html(images, output_dir: Path, author: str | None = None,
parts.append(film)
metadata_html = '