- Created overnight generation system
- Added master character references (Gronk, Kai)
- Implemented auto-commit for all generated assets
- Created comprehensive documentation and changelogs
- Setup FULL generator (850+ assets without NPCs)
- Added progress tracking and status check scripts
Ready for overnight mass generation 🌙
336 lines
12 KiB
Python
336 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
🎮 DOLINA SMRTI - ComfyUI Local Generator
|
|
Generates remaining assets with ComfyUI + rembg for transparent backgrounds
|
|
No API rate limits!
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import time
|
|
import uuid
|
|
import requests
|
|
import subprocess
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# Install rembg on first run if needed
|
|
try:
|
|
from rembg import remove
|
|
from PIL import Image
|
|
except ImportError:
|
|
print("Installing dependencies...")
|
|
os.system("pip3 install --user rembg pillow onnxruntime")
|
|
from rembg import remove
|
|
from PIL import Image
|
|
|
|
# Configuration
|
|
COMFYUI_URL = "http://127.0.0.1:8000"
|
|
OUTPUT_DIR = Path("/Users/davidkotnik/repos/novafarma/assets/images")
|
|
COMFYUI_OUTPUT = Path("/Users/davidkotnik/comfyui/output")
|
|
|
|
# Style - matches the 2D Indie Cartoon style
|
|
STYLE = "2D indie game sprite, cartoon vector style, clean smooth lines, full body visible from head to feet standing on ground, tight crop, isolated on pure white background #FFFFFF"
|
|
NEGATIVE = "pixel art, pixelated, voxel, 3d, realistic, photo, anime, manga, chibi, blurry, watermark, text, green background"
|
|
|
|
# ============================================================================
|
|
# REMAINING ASSETS TO GENERATE
|
|
# ============================================================================
|
|
|
|
ASSETS = [
|
|
# Remaining NPCs
|
|
("npcs", "npc_elder", "wise tribal elder NPC, walking stick, long white beard, robes"),
|
|
("npcs", "npc_child", "survivor orphan child NPC, oversized clothes, teddy bear"),
|
|
("npcs", "npc_cook", "camp cook NPC, pot and ladle, apron, big belly, friendly"),
|
|
("npcs", "npc_scout", "nomad scout NPC, binoculars, light leather armor, agile"),
|
|
|
|
# Animation frames
|
|
("npcs", "gronk_front_walk1", "Gronk troll, PINK dreadlocks, black baggy shirt pants, front view, left leg forward walking"),
|
|
("npcs", "gronk_front_walk2", "Gronk troll, PINK dreadlocks, black baggy shirt pants, front view, right leg forward walking"),
|
|
("npcs", "gronk_back_idle", "Gronk troll, PINK dreadlocks, black baggy shirt pants, back view, standing idle"),
|
|
("npcs", "kai_front_walk1", "Kai survivor, GREEN dreadlocks, blue jacket, front view, left leg forward walking"),
|
|
("npcs", "kai_front_walk2", "Kai survivor, GREEN dreadlocks, blue jacket, front view, right leg forward walking"),
|
|
("npcs", "kai_back_idle", "Kai survivor, GREEN dreadlocks, blue jacket, back view, standing idle"),
|
|
("npcs", "ana_front_walk1", "Ana explorer, PINK dreadlocks, brown vest, front view, left leg forward walking"),
|
|
("npcs", "ana_front_walk2", "Ana explorer, PINK dreadlocks, brown vest, front view, right leg forward walking"),
|
|
|
|
# Remaining animals
|
|
("zivali", "goat_brown", "brown goat farm animal, small horns, playful"),
|
|
("zivali", "rabbit_white", "white rabbit, long ears, fluffy"),
|
|
("zivali", "fox_red", "red fox wildlife, bushy tail, clever"),
|
|
("zivali", "golden_goose", "Golden Goose legendary, shimmering gold feathers"),
|
|
|
|
# Remaining mutants
|
|
("mutanti", "slime_red", "red fire slime monster, hot flames"),
|
|
("mutanti", "slime_purple", "purple poison slime, toxic bubbling"),
|
|
("mutanti", "mutant_rat_giant", "giant rat mutant, dog sized, diseased"),
|
|
("mutanti", "zombie_bloated", "bloated zombie, huge swollen, toxic drool"),
|
|
("mutanti", "zombie_armored", "armored zombie, riot gear, tough"),
|
|
("mutanti", "zombie_crawler", "crawler zombie, no legs, crawling"),
|
|
|
|
# Remaining bosses
|
|
("bosses", "boss_dragon_ice", "Ice Dragon boss, blue scales, frost breath"),
|
|
("bosses", "boss_phoenix", "Phoenix boss, giant fire bird, rebirth flames"),
|
|
("bosses", "boss_hydra", "Hydra boss, multi-headed serpent"),
|
|
("bosses", "boss_treant", "Elder Treant boss, giant living tree, nature"),
|
|
("bosses", "boss_demon", "Demon Prince boss, hellfire, horns"),
|
|
|
|
# Remaining items
|
|
("items", "tool_pickaxe_iron", "iron pickaxe mining tool"),
|
|
("items", "tool_shovel", "shovel digging tool"),
|
|
("items", "tool_hammer", "hammer building tool"),
|
|
("items", "weapon_bow_wood", "wooden bow with arrows"),
|
|
("items", "weapon_spear", "wooden spear, hunting weapon"),
|
|
("items", "weapon_crossbow", "crossbow ranged weapon"),
|
|
("items", "wand_lightning", "lightning magic wand, yellow crystal, sparks"),
|
|
("items", "wand_earth", "earth magic wand, green crystal, nature"),
|
|
("items", "wand_shadow", "shadow magic wand, purple crystal, darkness"),
|
|
("items", "wand_healing", "healing wand, white crystal, glow"),
|
|
("items", "potion_mana", "mana potion, blue liquid bottle"),
|
|
("items", "backpack_small", "small leather backpack"),
|
|
("items", "helmet_metal", "metal protective helmet"),
|
|
("items", "gas_mask", "gas mask with filters"),
|
|
("items", "food_bread", "bread loaf, fresh baked"),
|
|
("items", "food_apple", "red apple, fresh fruit"),
|
|
("items", "food_meat", "cooked meat steak"),
|
|
|
|
# Remaining environment
|
|
("environment", "tree_pine", "pine tree, evergreen, cone shaped"),
|
|
("environment", "tree_dead", "dead tree, no leaves, gnarled branches"),
|
|
("environment", "tree_cherry", "cherry blossom tree, pink flowers"),
|
|
("environment", "flower_red", "red flowers patch"),
|
|
("environment", "flower_blue", "blue flowers patch"),
|
|
("environment", "mushroom_red", "red spotted mushrooms"),
|
|
("environment", "fence_wood", "wooden fence section"),
|
|
("environment", "barrel_wood", "wooden barrel storage"),
|
|
("environment", "crate_wooden", "wooden shipping crate"),
|
|
("environment", "ruin_wall", "ruined brick wall section"),
|
|
("environment", "car_rusted", "rusted abandoned car wreck"),
|
|
("environment", "sign_warning", "warning sign post"),
|
|
]
|
|
|
|
|
|
def log(msg):
|
|
ts = datetime.now().strftime("%H:%M:%S")
|
|
print(f"[{ts}] {msg}")
|
|
sys.stdout.flush()
|
|
|
|
|
|
def git_commit(file_path, category, name):
|
|
"""Auto-commit generated asset to Git"""
|
|
try:
|
|
repo_dir = Path("/Users/davidkotnik/repos/novafarma")
|
|
rel_path = file_path.relative_to(repo_dir)
|
|
|
|
# Git add
|
|
subprocess.run(
|
|
["git", "add", str(rel_path)],
|
|
cwd=repo_dir,
|
|
check=True,
|
|
capture_output=True
|
|
)
|
|
|
|
# Git commit
|
|
commit_msg = f"🎨 Auto-generated asset: {category}/{name}"
|
|
result = subprocess.run(
|
|
["git", "commit", "-m", commit_msg],
|
|
cwd=repo_dir,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if result.returncode == 0:
|
|
log(f" 📝 Git committed")
|
|
return True
|
|
else:
|
|
# Možno da je že committed ali ni sprememb
|
|
return False
|
|
except Exception as e:
|
|
log(f" ⚠️ Git: {str(e)[:50]}")
|
|
return False
|
|
|
|
|
|
def create_workflow(prompt_text, output_name, size=512):
|
|
"""Create ComfyUI workflow"""
|
|
seed = int(time.time() * 1000) % 2147483647
|
|
full_prompt = f"{STYLE}, {prompt_text}"
|
|
|
|
return {
|
|
"1": {
|
|
"class_type": "CheckpointLoaderSimple",
|
|
"inputs": {"ckpt_name": "dreamshaper_8.safetensors"}
|
|
},
|
|
"2": {
|
|
"class_type": "EmptyLatentImage",
|
|
"inputs": {"width": size, "height": size, "batch_size": 1}
|
|
},
|
|
"3": {
|
|
"class_type": "CLIPTextEncode",
|
|
"inputs": {"text": full_prompt, "clip": ["1", 1]}
|
|
},
|
|
"4": {
|
|
"class_type": "CLIPTextEncode",
|
|
"inputs": {"text": NEGATIVE, "clip": ["1", 1]}
|
|
},
|
|
"5": {
|
|
"class_type": "KSampler",
|
|
"inputs": {
|
|
"seed": seed,
|
|
"steps": 30,
|
|
"cfg": 7.5,
|
|
"sampler_name": "euler_ancestral",
|
|
"scheduler": "karras",
|
|
"denoise": 1.0,
|
|
"model": ["1", 0],
|
|
"positive": ["3", 0],
|
|
"negative": ["4", 0],
|
|
"latent_image": ["2", 0]
|
|
}
|
|
},
|
|
"6": {
|
|
"class_type": "VAEDecode",
|
|
"inputs": {"samples": ["5", 0], "vae": ["1", 2]}
|
|
},
|
|
"7": {
|
|
"class_type": "SaveImage",
|
|
"inputs": {"filename_prefix": output_name, "images": ["6", 0]}
|
|
}
|
|
}
|
|
|
|
|
|
def queue_prompt(workflow):
|
|
try:
|
|
r = requests.post(
|
|
f"{COMFYUI_URL}/prompt",
|
|
json={"prompt": workflow, "client_id": f"ds_{uuid.uuid4().hex[:8]}"},
|
|
timeout=10
|
|
)
|
|
return r.json().get("prompt_id")
|
|
except Exception as e:
|
|
log(f"❌ Queue error: {e}")
|
|
return None
|
|
|
|
|
|
def wait_completion(prompt_id, timeout=120):
|
|
start = time.time()
|
|
while time.time() - start < timeout:
|
|
try:
|
|
r = requests.get(f"{COMFYUI_URL}/history/{prompt_id}", timeout=5)
|
|
data = r.json()
|
|
if prompt_id in data and data[prompt_id].get("outputs"):
|
|
return True
|
|
except:
|
|
pass
|
|
time.sleep(2)
|
|
return False
|
|
|
|
|
|
def download_and_process(prompt_id, output_path):
|
|
"""Download from ComfyUI and remove background with rembg"""
|
|
try:
|
|
h = requests.get(f"{COMFYUI_URL}/history/{prompt_id}").json()
|
|
for out in h.get(prompt_id, {}).get("outputs", {}).values():
|
|
for img in out.get("images", []):
|
|
r = requests.get(f"{COMFYUI_URL}/view", params={
|
|
"filename": img["filename"],
|
|
"subfolder": img.get("subfolder", ""),
|
|
"type": "output"
|
|
})
|
|
if r.status_code == 200:
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Load and remove background
|
|
from io import BytesIO
|
|
src_img = Image.open(BytesIO(r.content))
|
|
transparent_img = remove(src_img)
|
|
transparent_img.save(str(output_path), "PNG")
|
|
return True
|
|
return False
|
|
except Exception as e:
|
|
log(f"❌ Download error: {e}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
log("=" * 60)
|
|
log("🎮 DOLINA SMRTI - ComfyUI Local Generator")
|
|
log(" No API rate limits! All local processing!")
|
|
log("=" * 60)
|
|
|
|
# Check ComfyUI
|
|
try:
|
|
r = requests.get(f"{COMFYUI_URL}/system_stats", timeout=5)
|
|
v = r.json()["system"]["comfyui_version"]
|
|
log(f"✅ ComfyUI v{v} running")
|
|
except:
|
|
log("❌ ComfyUI not running! Start it first.")
|
|
return
|
|
|
|
# Get existing files
|
|
existing = set()
|
|
for cat, name, _ in ASSETS:
|
|
path = OUTPUT_DIR / cat / f"{name}.png"
|
|
if path.exists():
|
|
existing.add(name)
|
|
|
|
remaining = [(c, n, p) for c, n, p in ASSETS if n not in existing]
|
|
|
|
log(f"\n📊 Status:")
|
|
log(f" Total in registry: {len(ASSETS)}")
|
|
log(f" Already generated: {len(existing)}")
|
|
log(f" Remaining: {len(remaining)}")
|
|
|
|
if not remaining:
|
|
log("\n✅ All assets already generated!")
|
|
return
|
|
|
|
log(f"\n🚀 Starting generation of {len(remaining)} assets...")
|
|
log(" Each takes ~15-30 seconds\n")
|
|
|
|
success, fail = 0, 0
|
|
start_time = time.time()
|
|
|
|
for i, (cat, name, prompt) in enumerate(remaining, 1):
|
|
path = OUTPUT_DIR / cat / f"{name}.png"
|
|
|
|
log(f"[{i}/{len(remaining)}] 🎨 {name}...")
|
|
|
|
# Determine size
|
|
size = 512
|
|
if "boss_" in name:
|
|
size = 768
|
|
|
|
wf = create_workflow(prompt, name, size)
|
|
pid = queue_prompt(wf)
|
|
|
|
if pid and wait_completion(pid) and download_and_process(pid, path):
|
|
log(f" ✅ DONE (transparent)")
|
|
# Auto-commit to Git
|
|
git_commit(path, cat, name)
|
|
success += 1
|
|
else:
|
|
log(f" ❌ FAILED")
|
|
fail += 1
|
|
|
|
# Progress every 5
|
|
if i % 5 == 0:
|
|
elapsed = time.time() - start_time
|
|
per_asset = elapsed / i
|
|
remaining_time = per_asset * (len(remaining) - i)
|
|
log(f"\n📊 Progress: {i}/{len(remaining)} | ✅{success} ❌{fail}")
|
|
log(f" ETA: {remaining_time/60:.1f} min\n")
|
|
|
|
elapsed = time.time() - start_time
|
|
log("\n" + "=" * 60)
|
|
log("🎮 GENERATION COMPLETE!")
|
|
log(f" ✅ Success: {success}")
|
|
log(f" ❌ Failed: {fail}")
|
|
log(f" ⏱️ Time: {elapsed/60:.1f} min")
|
|
log(f" 📁 Output: {OUTPUT_DIR}")
|
|
log("=" * 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|