diff --git a/scripts/contact_sheet_registry.py b/scripts/contact_sheet_registry.py new file mode 100644 index 0000000..18343d9 --- /dev/null +++ b/scripts/contact_sheet_registry.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Tweet contact-sheet registry for the 201+ hand-drawn style gallery. + +G-category sheets are 4x4 (CAPACITY=16) contact sheets named +``G_{start:03}-{end:03}.png`` under ``images/``; the final sheet may be +incomplete. The active-sheet bookkeeping lives in +``handdraw-style-prompter/references/contact_sheet_state.json``. + +This module was previously missing from the published repository. The +implementation is reconstructed from the contracts enforced by +``handdraw-style-prompter/scripts/validate_library.py``. +""" +from __future__ import annotations + +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +IMAGES = ROOT / "images" +STATE_FILE = ROOT / "handdraw-style-prompter" / "references" / "contact_sheet_state.json" + +# 4x4 tile grid per contact sheet. +CAPACITY = 16 + +SHEET = re.compile(r"^G_(\d{3})(?:-(\d{3}))?\.png$") + + +def parse_sheet(path: Path) -> tuple[int, int] | None: + """Return (start, end) style numbers encoded in a G-sheet filename. + + A bare ``G_201.png`` counts as a single-style sheet (start == end). + Non-matching names return ``None`` so callers can skip them. + """ + match = SHEET.match(Path(path).name) + if not match: + return None + start = int(match.group(1)) + end = int(match.group(2)) if match.group(2) else start + if end < start: + return None + return start, end + + +def sheet_path(start: int, end: int) -> Path: + """Canonical path for the sheet covering styles ``start``–``end``.""" + if start == end: + return IMAGES / f"G_{start:03}.png" + return IMAGES / f"G_{start:03}-{end:03}.png" diff --git a/scripts/style_asset_paths.py b/scripts/style_asset_paths.py new file mode 100644 index 0000000..8732300 --- /dev/null +++ b/scripts/style_asset_paths.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Numbered style asset path helpers (single source of truth for layout). + +Assets live in 200-number buckets under ``images/individual/``: + +- ``001-200/001.png`` … ``401-600/xxx.png`` — per-style single images +- ``001-200/048_grid.jpg`` — optional four-panel grid, + same bucket as the matching single image; takes priority as a + generation reference when present. + +This module was previously missing from the published repository. The +implementation is reconstructed from the contracts enforced by +``handdraw-style-prompter/scripts/validate_library.py``. +""" +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +INDIVIDUAL = ROOT / "images" / "individual" +BUCKET_SIZE = 200 + + +def _number(value: int | str) -> int: + try: + number = int(value) + except (TypeError, ValueError): + raise ValueError(f"Style number must be an integer, got {value!r}") + if number < 1: + raise ValueError(f"Style number must be >= 1, got {number}") + return number + + +def bucket_name(value: int | str) -> str: + """Return the 200-style bucket directory name, e.g. 1 -> '001-200'.""" + number = _number(value) + start = (number - 1) // BUCKET_SIZE * BUCKET_SIZE + 1 + return f"{start:03}-{start + BUCKET_SIZE - 1:03}" + + +def bucket_dir(value: int | str) -> Path: + return INDIVIDUAL / bucket_name(value) + + +def single_path(value: int | str) -> Path: + """Path to the numbered single image, e.g. 48 -> .../001-200/048.png.""" + number = _number(value) + return bucket_dir(number) / f"{number:03}.png" + + +def grid_path(value: int | str) -> Path: + """Path to the four-panel grid image, e.g. 217 -> .../201-400/217_grid.jpg.""" + number = _number(value) + return bucket_dir(number) / f"{number:03}_grid.jpg"