Files
novafarma/scripts/generate_local_final.py
David Kotnik 117624befc 📝 Nočna Session Setup - Asset Generation Infrastructure
- 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 🌙
2025-12-29 03:43:44 +01:00

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()