Files
hoelee-blog/scripts/og-gen/migrate_to_v2_assets.py
T
hoelee 327d516ed9
Deploy / build (push) Successful in 17s
feat(art): replace all 72 posts' artwork with the v2 pipeline
Every post now carries one unique local-ComfyUI/Ideogram 4 base frame, composited
into EN + ZH share cards, a banner and list thumbs.

- the shipped prompts_catalog held only 21 distinct metaphors for 72 posts (16
  shared one visual), so all 72 were rewritten as unique object+state metaphors
  and 10 frames re-rolled after OCR found pseudo-text rendered at legible size
- render_from_art.mjs emits JPEG by default (558 KB PNG -> 84 KB mozjpeg q85);
  the ZH 1200x630 card did not exist before this batch
- 288 frontmatter fields migrated; the 144 v1 PNGs are gone
- PostList's thumb helper keys off the base slug (a ZH card is zh-<slug>.jpg while
  the thumb is <slug>-800.webp, which 404'd the whole ZH listing)
- [...slug].astro's <picture> regex accepts .jpg
- the repo now carries the art inputs (metaphors.json, captions/<slug>.json,
  refreshed prompts_catalog.json) so a frame is reproducible from git alone
2026-10-19 04:18:59 +08:00

74 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""migrate_to_v2_assets.py — point the content at the v2 artwork filenames.
The v2 renderer (render_from_art.mjs) writes JPEG/WebP, not PNG:
public/banners/<slug>.jpg (hero fallback; <picture> derives .webp)
public/og/<slug>.jpg (EN share card)
public/og/zh-<slug>.jpg (ZH share card)
Until this migration runs, every page still points at the v1 PNG paths, so it has
to happen in the SAME commit as the images or the site serves 404 heroes.
Idempotent: it only rewrites values that still look like the v1 paths, keeps each
file's quoting style, and prints what it changed. Run with --write to apply.
python scripts/og-gen/migrate_to_v2_assets.py # report only
python scripts/og-gen/migrate_to_v2_assets.py --write
"""
from __future__ import annotations
import argparse
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
POSTS = ROOT / "src" / "content" / "posts"
ZH = POSTS / "zh"
# ogImage: "/og/<slug>.png" or ogImage: /og/<slug>.png (quotes optional)
OG_RE = re.compile(r'^(ogImage:[ \t]*)(?P<q>"?)/og/(?P<name>[a-z0-9-]+)\.png(?P=q)[ \t]*$', re.M)
BANNER_RE = re.compile(r'^(banner:[ \t]*)(?P<q>"?)/banners/(?P<name>[a-z0-9-]+)\.png(?P=q)[ \t]*$', re.M)
def migrate(path: Path, zh: bool, write: bool) -> list[str]:
text = path.read_text(encoding="utf-8")
slug = path.stem
changes: list[str] = []
def og_sub(m: re.Match) -> str:
target = f"/og/zh-{slug}.jpg" if zh else f"/og/{slug}.jpg"
changes.append(f"ogImage {m.group(3)}.png -> {target}")
return f"{m.group(1)}{m.group('q')}{target}{m.group('q')}"
def banner_sub(m: re.Match) -> str:
target = f"/banners/{slug}.jpg"
changes.append(f"banner {m.group(3)}.png -> {target}")
return f"{m.group(1)}{m.group('q')}{target}{m.group('q')}"
new = OG_RE.sub(og_sub, text)
new = BANNER_RE.sub(banner_sub, new)
if new != text and write:
path.write_text(new, encoding="utf-8")
return changes
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--write", action="store_true")
args = ap.parse_args()
total = 0
files_changed = 0
for folder, zh in ((POSTS, False), (ZH, True)):
for p in sorted(folder.glob("*.md")):
ch = migrate(p, zh, args.write)
if ch:
files_changed += 1
total += len(ch)
print(f"{'WROTE' if args.write else 'would'} {p.relative_to(ROOT)}: " + "; ".join(ch))
print(f"\n{files_changed} files, {total} field(s) {'' if args.write else '(report only — pass --write)'}")
if __name__ == "__main__":
main()