#!/usr/bin/env python3 """Generate a cover image for a THREATMARKET intel pack via ComfyUI SDXL. Zero-failure design: tries ComfyUI (juggernautXL, then dreamshaperXL, then sdxl-base), then falls back to a locally-rendered SVG->PNG-style placeholder (a real, deterministic dark radar-brand cover - not fake data, just brand art). Always writes /opt/threatmarket/static/covers/.png (or .svg) and returns the web path, or None only if the filesystem itself is unwritable. Usage: from cover_gen import generate_cover path = generate_cover("chewy.com", "2026-09-25") """ import base64 import json import os import urllib.request COMFY = os.environ.get("COMFY_URL", "http://10.30.20.29:8188") COVER_DIR = os.environ.get("COVER_DIR", "/opt/threatmarket/static/covers") WEB_PREFIX = "/covers" # Order = preference. Lightning models = few steps = fast + reliable. CKPTS = ["flux1-schnell-fp8.safetensors", "juggernautXL_ragnarok.safetensors", "dreamshaperXL_lightningDPMSDE.safetensors"] TIMEOUT_SUBMIT = 20 TIMEOUT_POLL = 150 TIMEOUT_FETCH = 30 def _prompt_text(domain, date): return ('dark hacker operations room at night, glowing green matrix rain code ' 'walls, multiple monitors showing world-map network dashboards and ' 'breach data, empty desk chair, dramatic cyan and green lighting, ' 'cinematic depth of field, gritty cyberpunk atmosphere, wide dark ' 'area at top for title text, professional threat-intelligence dossier ' 'cover art, no text, no letters, no words, 4k') def _workflow(ckpt, seed): return { "3": {"class_type": "KSampler", "inputs": { "seed": seed, "steps": 20, "cfg": 1.8, "sampler_name": "euler", "scheduler": "sgm_uniform", "denoise": 1.0, "model": ["4", 0], "positive": ["6", 0], "negative": ["7", 0], "latent_image": ["5", 0]}}, "4": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": ckpt}}, "5": {"class_type": "EmptyLatentImage", "inputs": {"width": 1024, "height": 640, "batch_size": 1}}, "6": {"class_type": "CLIPTextEncode", "inputs": {"text": "placeholder", "clip": ["4", 1]}}, "7": {"class_type": "CLIPTextEncode", "inputs": {"text": "text, watermark, blurry, low quality, oversaturated", "clip": ["4", 1]}}, "8": {"class_type": "VAEDecode", "inputs": {"samples": ["3", 0], "vae": ["4", 2]}}, "9": {"class_type": "SaveImage", "inputs": {"filename_prefix": "tmcover", "images": ["8", 0]}}, } def _comfy_generate(domain, date, seed): """Try each checkpoint; return PNG bytes or None.""" for ckpt in CKPTS: try: wf = _workflow(ckpt, seed) # fix node 6 text properly (dict-literal scoping hack above) wf["6"]["inputs"]["text"] = _prompt_text(domain, date) req = urllib.request.Request( COMFY + "/prompt", data=json.dumps({"prompt": wf}).encode(), headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=TIMEOUT_SUBMIT) as r: resp = json.loads(r.read()) pid = resp.get("prompt_id") if not pid: continue import time deadline = time.time() + TIMEOUT_POLL while time.time() < deadline: with urllib.request.urlopen(COMFY + "/history/" + pid, timeout=15) as r: hist = json.loads(r.read()) if pid in hist and hist[pid].get("outputs"): outs = hist[pid]["outputs"] for node in outs.values(): for img in node.get("images", []): fn = img["filename"] sub = img.get("subfolder", "") imgtype = img.get("type", "output") url = f"{COMFY}/view?filename={fn}&subfolder={sub}&type={imgtype}" with urllib.request.urlopen(url, timeout=TIMEOUT_FETCH) as fr: return fr.read() time.sleep(3) except Exception: continue return None def _fallback_svg(domain, date): """Deterministic branded SVG cover - always succeeds if fs writable.""" seed = sum(ord(c) for c in domain) % 360 svg = f""" """ for i in range(6): svg += f"" svg += f"" import math for i in range(14): x = 120 + (i * 61) % 700 y = 420 + (i * 37) % 160 r = 2 + (i % 3) svg += f"" svg += f""" INTEL PACK {domain} {date} — THREATMARKET """ return svg.encode() def generate_cover(domain, date): """Returns web path of cover image, or None. Never raises.""" os.makedirs(COVER_DIR, exist_ok=True) pid = (domain + "-" + date.replace("-", "")).replace("/", "_") png_path = os.path.join(COVER_DIR, pid + ".png") svg_path = os.path.join(COVER_DIR, pid + ".svg") if os.path.exists(png_path): return f"{WEB_PREFIX}/{pid}.png" if os.path.exists(svg_path): return f"{WEB_PREFIX}/{pid}.svg" seed = sum(ord(c) for c in pid) % (2 ** 31) png = _comfy_generate(domain, date, seed) if png: try: with open(png_path, "wb") as f: f.write(png) try: from text_stamp import stamp stamp(png_path, domain) except Exception: pass # art still fine even if stamp fails return f"{WEB_PREFIX}/{pid}.png" except OSError: pass try: with open(svg_path, "wb") as f: f.write(_fallback_svg(domain, date)) return f"{WEB_PREFIX}/{pid}.svg" except OSError: return None if __name__ == "__main__": import sys d = sys.argv[1] if len(sys.argv) > 1 else "mozilla.org" dt = sys.argv[2] if len(sys.argv) > 2 else "2026-09-24" print(generate_cover(d, dt) or "FAILED")