← back to Crazy News Channel

tools/gen_images.py

51 lines

#!/usr/bin/env python3
"""Generate one news photo per story with local SDXL (free, runs on Apple MPS).

  ~/.venvs/sdxl/bin/python tools/gen_images.py prompts.json

prompts.json is a list of {"id": ..., "imagePrompt": ...}. Writes images/<id>.jpg
(800px wide JPEG). Skips ids that already have an image, so it is safe to re-run.
Weights: SDXL_CKPT env var, else the archived sd_xl_base_1.0 on /Volumes/Henry.
"""
import hashlib, json, os, sys, time

import torch
from diffusers import StableDiffusionXLPipeline

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "..", "images")
CKPT = os.environ.get("SDXL_CKPT") or \
    "/Volumes/Henry/mac2-archive/2026-06-26-reclaim/ComfyUI-checkpoints/sd_xl_base_1.0.safetensors"
STYLE = ("award-winning news photograph, photojournalism, natural light, 35mm, "
         "shallow depth of field, realistic, high detail")
NEGATIVE = ("text, words, letters, caption, watermark, logo, signage, typography, "
            "cartoon, illustration, painting, deformed, blurry, lowres, close-up face")


def main(path):
    items = json.load(open(path))
    os.makedirs(OUT, exist_ok=True)
    todo = [s for s in items if not os.path.exists(os.path.join(OUT, s["id"] + ".jpg"))]
    print(f"{len(items)} stories, {len(todo)} to generate", flush=True)
    if not todo:
        return
    device = "mps" if torch.backends.mps.is_available() else "cpu"
    pipe = StableDiffusionXLPipeline.from_single_file(
        CKPT, torch_dtype=torch.float16 if device == "mps" else torch.float32)
    pipe.to(device)
    pipe.set_progress_bar_config(disable=True)
    for i, s in enumerate(todo, 1):
        t0 = time.time()
        # Stable per-story seed, so a re-run of one id reproduces the same photo.
        seed = int(hashlib.sha1(s["id"].encode()).hexdigest()[:8], 16)
        img = pipe(prompt=f'{s["imagePrompt"]}, {STYLE}', negative_prompt=NEGATIVE,
                   width=1344, height=768, num_inference_steps=28, guidance_scale=6.5,
                   generator=torch.Generator("cpu").manual_seed(seed)).images[0]
        img = img.resize((800, 457))
        img.save(os.path.join(OUT, s["id"] + ".jpg"), quality=80, optimize=True)
        print(f"[{i}/{len(todo)}] {s['id']} {time.time() - t0:.0f}s", flush=True)


if __name__ == "__main__":
    main(sys.argv[1])