#!/usr/bin/env python3 """Call the qwen-edit-nsfw RunPod serverless endpoint SYNCHRONOUSLY (/runsync). Standalone: python 3.8+, stdlib only. Single-image edit on the Qwen-Rapid-AIO-NSFW v11.4 checkpoint (workflow qwen-edit-nsfw). python qwen_edit_nsfw_sync.py \ --api-key $RUNPOD_API_KEY \ --image person.jpg \ --prompt "replace the outfit with ..." \ --out ./results All arguments: --api-key RunPod API key (or set RUNPOD_API_KEY) --endpoint-id RunPod endpoint id (default: ENDPOINT_DEFAULT below) --image input image (required) --prompt edit instruction (required) --negative-prompt negative conditioning text --steps sampler steps (default 8) --seed integer seed (default: random, printed back) --megapixels working resolution in MP (default 1.0) --cfg / --eta / --sampler / --scheduler / --denoise sampler tuning --lora-next-scene [--lora-next-scene-strength] enable next-scene lora --workflow baked workflow name (default qwen-edit-nsfw) --set NODE.INPUT=VALUE raw graph override, repeatable --workflow-json FILE full API-format graph passthrough --out output directory (default .) --timeout max seconds (default 600) """ import argparse import base64 import json import os import pathlib import sys import time import urllib.error import urllib.request DEFAULT_ENDPOINT = "tplcz9jtzihoxa" def main(): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("--api-key", default=os.environ.get("RUNPOD_API_KEY")) ap.add_argument("--endpoint-id", default=DEFAULT_ENDPOINT) ap.add_argument("--image", required=True) ap.add_argument("--prompt", required=True) ap.add_argument("--negative-prompt") ap.add_argument("--steps", type=int) ap.add_argument("--seed", type=int) ap.add_argument("--megapixels", type=float) ap.add_argument("--cfg", type=float) ap.add_argument("--eta", type=float) ap.add_argument("--sampler") ap.add_argument("--scheduler") ap.add_argument("--denoise", type=float) ap.add_argument("--lora-next-scene", action="store_true") ap.add_argument("--lora-next-scene-strength", type=float, default=1.0) ap.add_argument("--workflow") ap.add_argument("--set", action="append", metavar="NODE.INPUT=VALUE") ap.add_argument("--workflow-json") ap.add_argument("--out", default=".") ap.add_argument("--timeout", type=float, default=600) args = ap.parse_args() if not args.api_key: sys.exit("ERROR: pass --api-key or set RUNPOD_API_KEY") params = {"prompt": args.prompt} for cli, p in [("negative_prompt", "negative_prompt"), ("steps", "steps"), ("seed", "seed"), ("megapixels", "megapixels"), ("cfg", "cfg"), ("eta", "eta"), ("sampler", "sampler"), ("scheduler", "scheduler"), ("denoise", "denoise")]: v = getattr(args, cli) if v is not None: params[p] = v if args.lora_next_scene: params["lora_next_scene"] = True params["lora_next_scene_strength"] = args.lora_next_scene_strength payload = { "images": [{"name": os.path.basename(args.image), "image": base64.b64encode(open(args.image, "rb").read()).decode()}], "params": params, } if args.workflow: payload["workflow"] = args.workflow if args.workflow_json: payload["workflow_json"] = json.load(open(args.workflow_json)) overrides = {} for s in args.set or []: k, v = s.split("=", 1) try: overrides[k] = json.loads(v) except json.JSONDecodeError: overrides[k] = v if overrides: payload["set"] = overrides req = urllib.request.Request( f"https://api.runpod.ai/v2/{args.endpoint_id}/runsync", data=json.dumps({"input": payload}).encode(), headers={"Content-Type": "application/json", "Authorization": f"Bearer {args.api_key}"}) t0 = time.monotonic() try: with urllib.request.urlopen(req, timeout=args.timeout) as r: result = json.load(r) except urllib.error.HTTPError as e: sys.exit(f"ERROR: HTTP {e.code}: {e.read().decode(errors='replace')[:2000]}") # /runsync answers early (IN_QUEUE/IN_PROGRESS) when a job outlives the # sync window (~90 s), e.g. during a cold start — fall back to polling TERMINAL = ("COMPLETED", "FAILED", "CANCELLED", "TIMED_OUT") while result.get("status") not in TERMINAL and result.get("id"): if time.monotonic() - t0 > args.timeout: sys.exit(f"ERROR: timed out after {args.timeout}s " f"(job {result['id']} status {result.get('status')})") time.sleep(3) poll = urllib.request.Request( f"https://api.runpod.ai/v2/{args.endpoint_id}/status/{result['id']}", headers={"Authorization": f"Bearer {args.api_key}"}) with urllib.request.urlopen(poll, timeout=90) as r: result = json.load(r) if result.get("status") != "COMPLETED": sys.exit(f"ERROR: {json.dumps(result, indent=2)[:3000]}") output = result["output"] if "error" in output: sys.exit(f"ERROR from handler: {output['error']}") out_dir = pathlib.Path(args.out) out_dir.mkdir(parents=True, exist_ok=True) for i, img in enumerate(output["images"]): p = out_dir / f"{int(time.time())}-{i}-{img['filename']}" p.write_bytes(base64.b64decode(img["data"])) print(p) print(f"# seed={output.get('seed')} wall={time.monotonic()-t0:.1f}s " f"delay={result.get('delayTime')}ms exec={result.get('executionTime')}ms", file=sys.stderr) if __name__ == "__main__": main()