Add rendering

Refactors the generator do to a two-pass:
1. Get a grasp of all directories which have images, noting their
   children and images
2. Do stuff with those directories
This commit is contained in:
Nick Pegg 2024-08-03 17:05:17 -07:00
parent eb828dc48b
commit 4f5bd76f8a
4 changed files with 90 additions and 35 deletions

View file

@ -19,7 +19,7 @@ fmt:
poetry run ruff format poetry run ruff format
lint: lint:
poetry run ruff check poetry run ruff check --fix
test: test:
poetry run mypy . poetry run mypy .

View file

@ -1,38 +1,85 @@
import logging import logging
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from pprint import pformat
from typing import Iterator
from jinja2 import Environment, FileSystemLoader, select_autoescape
from PIL import Image, UnidentifiedImageError from PIL import Image, UnidentifiedImageError
from rich.progress import track, Progress from rich.console import Console
from rich.progress import track
from photoalbum.config import Config from photoalbum.config import Config
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@dataclass
class ImageDirectory:
path: Path
children: list["ImageDirectory"]
images: list[Path]
is_root: bool = False
def walk(self) -> Iterator["ImageDirectory"]:
yield self
for child in self.children:
yield from child.walk()
def image_paths(self) -> list[Path]:
"""
Iterate through all images in this dir and children
"""
images = []
for image_dir in self.walk():
images += image_dir.images
return images
def generate(config: Config, album_path: Path) -> None: def generate(config: Config, album_path: Path) -> None:
""" """
Main generation function Main generation function
""" """
images = find_images(album_path) root_dir = find_images(album_path)
generate_thumbnails(config, images) logger.debug(pformat(root_dir))
generate_html(config, album_path, images) generate_thumbnails(config, root_dir)
generate_html(config, root_dir)
def find_images(path: Path) -> list[Path]: def find_images(root_path: Path) -> ImageDirectory:
""" """
Returns paths of all images in the given path Build up an ImageDirectory to track all of the directories and their images.
A directory with no images nor childern with images will not be tracked, since we
don't want to render an album page for those.
""" """
images = [] # image_dirs keeps track of all directories we find with images in them, so we can
for parent_path, dirnames, filenames in path.walk(): # attach them as children to parent directories
if parent_path.name == "slides": image_dirs: dict[Path, ImageDirectory] = {
root_path: ImageDirectory(path=root_path, children=[], images=[], is_root=True)
}
for dirpath, dirnames, filenames in root_path.walk(top_down=False):
if dirpath.name in {"slides", "_templates", "static"}:
continue continue
for filename in filenames: image_dir = image_dirs.get(
file_path = parent_path / filename dirpath, ImageDirectory(path=dirpath, children=[], images=[])
if is_image(file_path): )
images.append(file_path)
return images for dirname in sorted(dirnames):
child_path = dirpath / dirname
if child_path in image_dirs:
image_dir.children.append(image_dirs[child_path])
for filename in sorted(filenames):
file_path = dirpath / filename
if is_image(file_path):
image_dir.images.append(file_path)
image_dirs[image_dir.path] = image_dir
return image_dirs[root_path]
def is_image(path: Path) -> bool: def is_image(path: Path) -> bool:
@ -46,11 +93,12 @@ def is_image(path: Path) -> bool:
return False return False
def generate_thumbnails(config: Config, images: list[Path]) -> None: def generate_thumbnails(config: Config, root_dir: ImageDirectory) -> None:
""" """
Find all of the images and generate thumbnails and on-screen versions Find all of the images and generate thumbnails and on-screen versions
""" """
for image_path in track(images, description="Making thumbnails..."): for image_path in track(root_dir.image_paths(), description="Making thumbnails..."):
logger.debug(image_path)
orig_img = Image.open(image_path) orig_img = Image.open(image_path)
slides_path = image_path.parent / "slides" slides_path = image_path.parent / "slides"
@ -60,34 +108,41 @@ def generate_thumbnails(config: Config, images: list[Path]) -> None:
thumb_img.thumbnail(config.thumbnail_size) thumb_img.thumbnail(config.thumbnail_size)
thumb_filename = image_path.stem + ".thumb" + image_path.suffix thumb_filename = image_path.stem + ".thumb" + image_path.suffix
thumb_img.save(slides_path / thumb_filename) thumb_img.save(slides_path / thumb_filename)
logger.info(f"Generated thumbnail size \"{image_path}\" -> \"{thumb_filename}\"") logger.info(f'Generated thumbnail size "{image_path}" -> "{thumb_filename}"')
screen_img = orig_img.copy() screen_img = orig_img.copy()
screen_img.thumbnail(config.view_size) screen_img.thumbnail(config.view_size)
screen_filename = image_path.stem + ".screen" + image_path.suffix screen_filename = image_path.stem + ".screen" + image_path.suffix
screen_img.save(slides_path / screen_filename) screen_img.save(slides_path / screen_filename)
logger.info(f"Generated screen size \"{image_path}\" -> \"{screen_filename}\"") logger.info(f'Generated screen size "{image_path}" -> "{screen_filename}"')
def generate_html(config: Config, root_path: Path, images: list[Path]) -> None: def generate_html(config: Config, root_dir: ImageDirectory) -> None:
""" """
Recursively generate HTML files for this directory and all children Recursively generate HTML files for this directory and all children
""" """
jinja_env = Environment(
loader=FileSystemLoader(root_dir.path / "_templates"),
autoescape=select_autoescape(),
)
# Find all the album dirs that either have photos, or have child album dirs album_tmpl = jinja_env.get_template("album.html")
# The paths in this set are all relative to the root_path photo_tmpl = jinja_env.get_template("photo.html")
album_paths = {Path('.')}
for image_path in images:
rel_path = image_path.relative_to(root_path)
for parent in rel_path.parents:
album_paths.add(parent)
# Generate album pages for all album dirs with Console().status("Rendering HTML..."):
for album_path in track(album_paths, description="Generating album HTML..."): for album_dir in root_dir.walk():
html_path = album_path / "index.html" html_path = album_dir.path / "index.html"
logger.debug(html_path) logger.debug(f"Rendering {html_path}")
with html_path.open("w") as f:
f.write(album_tmpl.render())
# Generate photo pages for all photos for image_path in album_dir.images:
for image_path in track(images, description="Generating photo HTML..."): # TODO: If a file with a matching name but .txt or .md, add that as the
html_path = image_path.parent / "slides" / image_path.with_suffix(".html").name # description for the image
logger.debug(html_path) html_path = (
image_path.parent / "slides" / image_path.with_suffix(".html").name
)
html_path.parent.mkdir(exist_ok=True)
logger.debug(f"Rendering {html_path}")
with html_path.open("w") as f:
f.write(photo_tmpl.render())