Des images en lot depuis un tableur
Lisez des prompts dans un fichier CSV, créez une image par ligne et enregistrez les fichiers à côté. Idéal pour les photos produit, les miniatures et les variantes publicitaires.
Comment ça marche
- Écrivez un prompt par ligne dans prompts.csv, sous un en-tête nommé prompt.
- Lancez le script avec POPNOW_API_KEY défini.
- Les images arrivent dans le dossier out, nommées d’après le numéro de ligne.
# pip install requests
import csv, hashlib, os, pathlib, time
import requests
MODEL = "google/nano-banana-2.1/text-to-image"
OPTIONS = {
"aspect_ratio": "16:9",
"resolution": "1k",
"output_format": "png"
}
def main():
out = pathlib.Path("out")
out.mkdir(exist_ok=True)
with open("prompts.csv", newline="", encoding="utf-8") as f:
rows = [r["prompt"] for r in csv.DictReader(f) if r.get("prompt")]
# Submit every row first, then collect: the images render in parallel.
tasks = []
for i, prompt in enumerate(rows, start=1):
# Same row, same key: rerunning after a crash never pays twice.
key = "batch-" + hashlib.sha256(f"{i}:{prompt}".encode()).hexdigest()[:32]
payload = {"model": MODEL, "input": {"prompt": prompt, **OPTIONS}}
tasks.append((i, create(payload, key)))
for i, task_id in tasks:
save(wait(task_id), out / f"{i:03d}.png")
print("saved", i)
# ---- helpers ----
BASE = "https://api.popnow.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['POPNOW_API_KEY']}"}
def call(method, path, **kwargs):
headers = {**HEADERS, **kwargs.pop("headers", {})}
body = requests.request(method, BASE + path, headers=headers, timeout=60, **kwargs).json()
if body["code"] != 200:
raise RuntimeError(f'{body["code"]} {body["msg"]}')
return body["data"]
def create(payload, key):
return call("POST", "/jobs/createTask", json=payload, headers={"Idempotency-Key": key})["taskId"]
def wait(task_id):
delay = 2.0
while True:
time.sleep(delay)
task = call("GET", "/jobs/recordInfo", params={"taskId": task_id})
assets = (task.get("output") or {}).get("assets") or []
copying = any(a.get("pending") and not a.get("unavailable") for a in assets)
if task["state"] in ("succeeded", "failed", "canceled", "expired") and not copying:
return task
delay = min(delay * 1.5, 15.0)
def save(task, path):
if task["state"] != "succeeded":
raise RuntimeError(f'{task["state"]}: {task.get("errorMessage", "no detail")}')
url = task["output"]["assets"][0]["url"]
with open(path, "wb") as f:
f.write(requests.get(url, timeout=300).content)
if __name__ == "__main__":
main()
