← back to Filibuster Marathon Cartoon

anim/edit.py

32 lines

import base64,json,os,time,urllib.request
tok=[l.split('=',1)[1].strip().strip('"\'') for l in open(os.path.expanduser('~/Projects/secrets-manager/.env')) if l.startswith('REPLICATE_API_TOKEN=')][0]
def api(url,data=None):
    r=urllib.request.Request(url,data=json.dumps(data).encode() if data else None,headers={"Authorization":f"Bearer {tok}","Content-Type":"application/json","Prefer":"wait"})
    return json.load(urllib.request.urlopen(r,timeout=300))
HERE=os.path.dirname(os.path.abspath(__file__))  # paths resolve from this file, not the cwd
def P(name): return os.path.join(HERE,name)
def poll(pr,interval,deadline_s):
    # Replicate bills at create time: never lose the id, retry transient poll errors, bound the wait.
    end=time.time()+deadline_s
    while pr["status"] not in("succeeded","failed","canceled"):
        if time.time()>end: raise SystemExit(f"TIMEOUT after {deadline_s}s — recover later: GET {pr['urls']['get']}")
        time.sleep(interval)
        for a in range(5):
            try: pr=api(pr["urls"]["get"]); break
            except Exception as e:
                print(f"poll error (attempt {a+1}/5): {e}"); time.sleep(interval*(a+1))
        else: raise SystemExit(f"poll failed 5x — recover later: GET {pr['urls']['get']}")
    return pr
img="data:image/jpeg;base64,"+base64.b64encode(open(P('base.jpg'),'rb').read()).decode()
p=("Keep this exact pen-and-ink editorial cartoon, same style, same delegate, same lectern, same telephone directory, same composition. "
   "Edit only: 1) the melted wall clock's minute hand slides off its face and hangs down the wall like a limp noodle all the way to the floor, its tip resting in the puddle on the floor at bottom right; "
   "2) remove the tangled shape lying in the puddle at bottom right, replace it with plain floor and puddle; "
   "3) the rows of legislators in the background are asleep, heads slumped forward onto their chests. No text added, no signature.")
pr=api("https://api.replicate.com/v1/models/black-forest-labs/flux-kontext-pro/predictions",{"input":{"prompt":p,"input_image":img,"aspect_ratio":"match_input_image","output_format":"jpg"}})
print("id",pr["id"],pr["status"])
pr=poll(pr,3,900)
print(pr["status"],pr.get("error"))
if pr["status"]!="succeeded": raise SystemExit(1)
out=pr["output"]; out=out[0] if isinstance(out,list) else out
urllib.request.urlretrieve(out,P('edited.jpg')); print('saved')