Six standalone scripts. Each one reads the files the previous stage wrote and
writes the files the next one reads. The zip is the input to fal.ai’s hosted
krea-2-trainer.
scrape.py harvests 640px thumbnails from Wikimedia Commons “Taken with…”
categories for eleven cameras — Nokia N95, Sony Ericsson Aino, C902, C905, and
C702, Canon PowerShot A80, A70, and A95, Sony DSC-P200 and DSC-P8, Kodak
EasyShare C743 — capped at 1,000 per category. A file is kept only if its
license is CC0, public domain, CC BY, or CC BY-SA. An EXIF date outside
2003–2010 is rejected; a missing date is kept and flagged. Each kept file is a
row in manifest.csv.
rank.py scores those thumbnails with SigLIP 2: mean cosine similarity to six
candid, flash-lit, party-scene prompts minus mean similarity to six encyclopedic
landmark prompts. The score is a sort order, written to scores.csv.
gallery.py writes one static HTML file, sorted by that score. A click cycles
keep, reject, unmarked. Export downloads keepers.json. The committed cuts are
160 combined, 122 cameraphone, and 38 compact digicam.
fetch.py downloads the original for each keeper into data/fullres/, skipping
files already on disk.
caption.py asks MiniCPM-V, served locally through Ollama, for the literal
content and forbids style, camera, and era. A caption that is too short, or one
word repeated past a fixed count and fraction, is rejected. A rejected or
unreachable reply falls back to Qwen3-VL, which sees a 1024×1024 copy, reloads
for every image, and starts each reply with one of six openers chosen from the
filename. Sidecars are written as content plus a trigger. The default trigger is
y2k digicam snapshot style.
package.py will not write the zip unless every keeper has an image and a
caption sidecar. Beside the zip it writes an attribution CSV. Passing a keepers file and an output name packages one cut from the same
full-resolution pool.