Files
calvana/gen_kaffarah.py
Omair Saleh ef37ca0c18 Fix payment flexibility quote length and orphan word
- Shorten quote 03 from 'Can I split it across a few months?' to 'Can I pay monthly?' for column symmetry
- Add nbsp between 'money' and 'arriving' to prevent orphan line break
2026-03-04 13:58:42 +08:00

145 lines
6.9 KiB
Python

"""
Generate 3 on-brand kaffarah-community.jpg replacements.
Uses anti-AI prompting strategy from cr-brand-style.json.
"""
import sys, io, os, time, json
sys.stdout.reconfigure(encoding='utf-8')
from google import genai
from google.genai import types
from PIL import Image
API_KEY = "AIzaSyCHnesXLjPw-UgeZaQotut66bgjFdvy12E"
MODEL = "gemini-3-pro-image-preview"
OUT = "screenshots/kaffarah-replacements"
os.makedirs(OUT, exist_ok=True)
client = genai.Client(api_key=API_KEY)
# ── ANTI-AI STYLE BLOCK ──
# No camera names, no "golden hour", no emotion words, no "bokeh".
# Describe PHYSICS of light, SPECIFIC objects, ACTIONS not feelings.
STYLE = (
"Raw documentary photograph with visible film grain and slight color noise in the shadows. "
"The color is MUTED — pulled-back saturation, faded dusty quality, blacks slightly lifted, "
"NOT warm amber or orange-tinted. Highlights have a faint yellow-green cast. "
"Shadows lean cool and neutral. "
"Single hard directional light source creating deep shadows on one side. "
"Unlit areas stay DARK, not filled in. "
"The framing is IMPERFECT — a partial figure or object intrudes at one edge of the frame. "
"The subject is slightly off-center. There is foreground obstruction. "
"Skin has visible pores, uneven tone, slight dust. Hair is uncombed. "
"Clothing is thin worn cotton, faded, creased, with visible stitching. "
"The environment is CLUTTERED with real objects at multiple depth planes. "
"NO smooth skin. NO perfect composition. NO warm glow. NO clean backgrounds. "
"Aspect ratio: exactly 2:1 wide landscape. "
)
PROMPTS = {
"kaffarah-v1.jpg": (
STYLE +
"Three children sit on a swept-dirt floor against a crumbling plastered wall eating from "
"shared metal thali plates. A boy around 8 is mid-chew, mouth slightly open, looking "
"sideways at something outside the frame. His faded blue cotton kurta has a torn collar. "
"Next to him a younger girl scoops rice with her right hand, not looking up. Her hair is "
"tangled and hasn't been brushed. Behind them, a wooden charpai with sagging rope webbing, "
"a plastic water jug, and a torn calendar hanging crooked on the wall. Afternoon sun comes "
"through a doorway on the left, casting a hard beam across the floor — the far wall stays "
"in deep shadow. Someone's bare foot and ankle are visible at the bottom-right edge of the "
"frame, cropped off. Dust motes visible in the light beam. The floor has a cracked cement "
"patch and a worn woven mat. Muted desaturated color with visible grain."
),
"kaffarah-v2.jpg": (
STYLE +
"A boy around 6 sits at a rough wooden bench eating rice and dal from a dented steel plate. "
"His hand is in the food, fingers pressing rice together. He is looking down at his plate, "
"not at the camera. His dark hair sticks up on one side where he slept on it. He wears a "
"faded brown cotton shirt buttoned wrong — one side hangs lower than the other. His "
"fingernails have dirt under them. The bench has deep scratches and a water ring stain. "
"Behind him, a plastered wall with a long crack running diagonally, a nail with nothing "
"on it, and a small high window letting in a hard shaft of light from the right. Two "
"other children are visible in the mid-ground, slightly out of focus, also eating. "
"An adult's elbow and forearm in a grey cotton sleeve intrudes into the left edge of "
"frame. On the bench next to the boy: a scratched steel cup with water. The light "
"illuminates only the right side of his face. The left side falls into shadow. "
"Muted color, visible grain, slight chromatic aberration at contrast edges."
),
"kaffarah-v3.jpg": (
STYLE +
"Seen from slightly behind and to the side of a woman in a faded cream dupatta who is "
"setting down a metal plate of food in front of a small child seated on a woven mat. "
"We see the woman's hands and forearms — veins visible, a thin glass bangle on one wrist. "
"The child, about 5, reaches for the plate with both small hands. The child's face is in "
"three-quarter profile, slightly blurred because the focus is on the hands and the plate. "
"The food is simple — a mound of rice, yellow dal, a piece of flatbread folded on the side. "
"The mat has fraying edges and a cigarette burn mark. Against the wall behind them: a "
"stacked row of steel plates, a plastic bag hanging on a nail, peeling turquoise paint "
"revealing brown plaster underneath. Hard afternoon light from a window on the right. "
"Another child sits further back, eating, almost lost in the dim background. A power "
"cable runs along the top of the wall. Muted, slightly desaturated, dusty color. "
"Fine grain throughout. This is a REAL moment, not posed."
),
}
def generate_and_save(filename, prompt, max_retries=3):
for attempt in range(max_retries):
try:
t0 = time.time()
print(f" [{attempt+1}/{max_retries}] {filename}...")
resp = client.models.generate_content(
model=MODEL,
contents=prompt,
config=types.GenerateContentConfig(
response_modalities=["IMAGE", "TEXT"],
temperature=1.0,
),
)
for part in resp.candidates[0].content.parts:
if part.inline_data and part.inline_data.mime_type.startswith("image/"):
img = Image.open(io.BytesIO(part.inline_data.data))
if img.mode != "RGB":
img = img.convert("RGB")
path = os.path.join(OUT, filename)
# Compress to stay under 2 MB
for q in [88, 82, 75, 65]:
img.save(path, "JPEG", quality=q, optimize=True, progressive=True)
if os.path.getsize(path) < 2_000_000:
break
sz = os.path.getsize(path)
print(f" [OK] {filename} -- {img.width}x{img.height}, {sz//1024} KB, {time.time()-t0:.1f}s")
return True
print(f" [WARN] no image in response")
except Exception as e:
print(f" [ERR] {str(e)[:200]}")
if attempt < max_retries - 1:
time.sleep(5 * (attempt + 1))
print(f" [FAIL] {filename}")
return False
if __name__ == "__main__":
print(f"Generating {len(PROMPTS)} kaffarah replacements -> {os.path.abspath(OUT)}\n")
ok = 0
for fn, prompt in PROMPTS.items():
if generate_and_save(fn, prompt):
ok += 1
time.sleep(2)
print(f"\nDone: {ok}/{len(PROMPTS)}")
for f in sorted(os.listdir(OUT)):
sz = os.path.getsize(os.path.join(OUT, f))
print(f" {f} -- {sz//1024} KB")