Content - 100 new plates under content/posts/02/ — one recurring red-haired model across 10 editorial registers (Haute Couture, Film Noir, Boudoir, Avant-Garde, Minimalism, Baroque, Street Style, Surrealism, Monochrome, Nocturne), generated with flux-1.1-pro, face-swapped to a consistent identity (FaceFusion), and upscaled (Upscayl + Remacri, 3328x4992). - content/issues/02/_index.md; issue 01 -> status archived; hugo.toml params (issueIds newest-first, issueNumber/Name/Season/Blurb). Structure - Plate bundles grouped by issue: content/posts/<issue>/<slug>/, with build.render:never stubs so /posts/<issue>/ isn't emitted. nginx 301s the legacy flat /posts/<slug>/ URLs to /posts/01/<slug>/. The /posts/ archive uses .RegularPagesRecursive. Image pipeline (scripts/, data/ gitignored) - generate-images.py — Replicate flux-1.1-pro, 960x1440, idempotent. - faceswap-images.py — FaceFusion batch-run, one consistent face. - upscale-images.py — Upscayl + remacri-4x, long edge clamped to 4992. - build-issue.py — prompts JSON -> content/posts/<issue>/*/index.md. Build - CSS now compiled by Hugo's css.TailwindCSS (partials/css.html + templates.Defer); dropped the standalone Tailwind CLI step, concurrently, and the gitignored static/css/main.css. Requires Hugo >= 0.161; Dockerfile collapsed to a single hugomods/hugo:debian-node build stage. pnpm-workspace.yaml: preferSymlinkedExecutables + allowBuilds. Front end - Pagination 8 per page. - Lightbox: brand mark and category link out; robust SPA back/forward (fetch before startViewTransition, single-flight guard, swallow the transition abort rejection, popstate re-renders / reopens the viewer). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DZPmxGywFnAhmYJB1eh9fm
82 lines
2.3 KiB
Python
Executable File
82 lines
2.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Import posts from ~/projects/ginger/posts.csv into Hugo content.
|
|
Only imports posts whose generated_image exists in the selected images folder.
|
|
Run from the site root: python3 scripts/import-posts.py
|
|
"""
|
|
import csv
|
|
import os
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
GINGER = Path.home() / "projects" / "ginger"
|
|
CSV_PATH = GINGER / "posts.csv"
|
|
IMG_DIR = GINGER / "images" / "final" / "selected"
|
|
SITE = Path(__file__).parent.parent
|
|
CONTENT = SITE / "content" / "posts" / "01"
|
|
|
|
CONTENT.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Collect available images
|
|
available = {f.name for f in IMG_DIR.iterdir() if f.is_file()}
|
|
|
|
matched = []
|
|
with open(CSV_PATH, newline="", encoding="utf-8") as f:
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
gen = row.get("generated_image", "").strip()
|
|
if gen and gen in available:
|
|
matched.append(row)
|
|
|
|
print(f"Matched {len(matched)} posts out of {len(available)} available images")
|
|
|
|
# Sort deterministically by generated_image filename for stable plate numbers
|
|
matched.sort(key=lambda r: r["generated_image"])
|
|
|
|
for idx, row in enumerate(matched, start=1):
|
|
plate = f"{idx:03d}"
|
|
gen_img = row["generated_image"].strip()
|
|
slug = gen_img.replace(".png", "")
|
|
title = row["title"].strip()
|
|
desc = row["description"].strip()
|
|
if desc:
|
|
desc = desc[0].upper() + desc[1:]
|
|
if not desc.endswith('.'):
|
|
desc += '.'
|
|
desc = desc.replace('"', '\\"')
|
|
raw_cat = row.get("category", "").strip()
|
|
raw_tags = row.get("tags", "").strip()
|
|
|
|
cats = [raw_cat.split(",")[0].strip()] if raw_cat.strip() else ["Uncategorised"]
|
|
tags = [t.strip() for t in raw_tags.split(",") if t.strip()]
|
|
|
|
cats_yaml = "\n".join(f' - "{c}"' for c in cats)
|
|
tags_yaml = "\n".join(f' - "{t}"' for t in tags) if tags else ' []'
|
|
|
|
bundle = CONTENT / slug
|
|
bundle.mkdir(exist_ok=True)
|
|
|
|
src_img = IMG_DIR / gen_img
|
|
dst_img = bundle / gen_img
|
|
if not dst_img.exists():
|
|
shutil.copy2(src_img, dst_img)
|
|
|
|
md = f"""---
|
|
title: "{title.replace('"', '\\"')}"
|
|
description: "{desc}"
|
|
plate: "{plate}"
|
|
slug: "{slug}"
|
|
issues:
|
|
- "01"
|
|
image: "{gen_img}"
|
|
weight: {idx}
|
|
categories:
|
|
{cats_yaml}
|
|
tags:
|
|
{tags_yaml}
|
|
---
|
|
"""
|
|
(bundle / "index.md").write_text(md, encoding="utf-8")
|
|
|
|
print(f"Created {len(matched)} page bundles in {CONTENT}")
|