Files
novafarma/scripts/auto_generate_overnight.py
David Kotnik b46ea5ab88 📋 Preparation for next phase
While waiting for quota reset (01:40):
- Created Mythical Highlands manifest (95 assets ready)
- Production summary for today
- Updated automation script (timezone fix)

Current status:
- Dino Valley: 91/109 (83%)
- Automation running, will complete at 01:50
- Next: Mythical Highlands (€38, ~2h)

Total progress: 254/14,000 (1.81%)
2026-01-03 00:32:59 +01:00

254 lines
9.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""
OVERNIGHT AUTOMATION SCRIPT - Dino Valley & Beyond
Waits for quota reset, then automatically generates remaining assets
"""
import os
import time
import json
import requests
from datetime import datetime, timedelta, timezone
from pathlib import Path
import base64
# ========================================
# CONFIGURATION
# ========================================
PROJECT_ROOT = Path("/Users/davidkotnik/repos/novafarma")
ASSET_ROOT = PROJECT_ROOT / "assets/slike"
LOG_FILE = PROJECT_ROOT / "auto_generation_log.txt"
# Vertex AI Configuration
PROJECT_ID = "gen-lang-client-0428644398"
LOCATION = "us-central1"
MODEL = "imagen-3.0-generate-001"
# Quota reset time (from error message)
QUOTA_RESET_TIME = datetime.fromisoformat("2026-01-03T01:40:39+01:00")
# Generation limits
MAX_IMAGES_PER_HOUR = 60 # Conservative (actual is 60/min)
BATCH_SIZE = 10 # Generate in batches for checkpointing
DELAY_BETWEEN_IMAGES = 1.2 # Seconds (safe rate limiting)
# Style definitions
STYLE_33_ENTITIES = """exact same art style as reference. Very thick black outlines (4-5px), flat colors NO gradients, pastel colors with dark gothic accents, NO pupils (white eyes), cute-dark aesthetic, clean vector cartoon"""
STYLE_30_VEGETATION = """exact Garden Story cozy art style. Soft rounded shapes, gentle pastel colors, NO thick outlines (thin 1-2px), watercolor-like soft shading, cute botanical illustration, clean cozy plant art"""
# ========================================
# REMAINING DINO VALLEY ASSETS (18)
# ========================================
DINO_VALLEY_REMAINING = {
"creatures": [
{
"name": "boss_thunder_raptor",
"prompt": "Thunder Raptor boss in {STYLE}. Pastel purple/blue raptor with lightning patterns, large empty white eyes NO pupils, dark gothic electric accents. 3/4 angle. Chroma green background (#00FF00)."
},
{
"name": "dino_nest_eggs_special",
"prompt": "Special glowing dinosaur nest in {STYLE}. Pastel brown nest with magical glowing eggs, dark gothic mystical aura. 3/4 angle. Chroma green background (#00FF00)."
},
{
"name": "zombie_prehistoric",
"prompt": "Prehistoric zombie caveman in {STYLE}. Pastel green/gray decayed caveman, torn tribal clothes, large empty white eyes NO pupils, dark gothic rot. 3/4 angle. Chroma green background (#00FF00)."
},
{
"name": "zombie_dino_raptor",
"prompt": "Zombie raptor dinosaur in {STYLE}. Pastel gray/green undead raptor with exposed bones, large empty white eyes NO pupils, dark gothic decay. 3/4 angle. Chroma green background (#00FF00)."
},
{
"name": "zombie_bone",
"prompt": "Skeleton zombie in {STYLE}. White/cream animated skeleton warrior, large empty eye sockets, dark gothic cracks. 3/4 angle. Chroma green background (#00FF00)."
},
{
"name": "zombie_tar",
"prompt": "Tar zombie in {STYLE}. Black sticky tar monster humanoid, glowing pastel orange eyes, dark gothic dripping texture. 3/4 angle. Chroma green background (#00FF00)."
},
{
"name": "npc_tribal_hunter",
"prompt": "Tribal hunter NPC in {STYLE}. Pastel brown skinned caveman with bone spear, tribal tattoos, large empty white eyes NO pupils, dark gothic war paint, friendly stance. 3/4 angle. Chroma green background (#00FF00)."
},
{
"name": "npc_shaman",
"prompt": "Tribal shaman NPC in {STYLE}. Elderly caveman with bone staff and feather headdress, pastel skin, large empty white eyes NO pupils, dark gothic mystical symbols, wise pose. 3/4 angle. Chroma green background (#00FF00)."
},
]
}
# ========================================
# UTILITY FUNCTIONS
# ========================================
def log(message):
"""Log message to file and console"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_msg = f"[{timestamp}] {message}"
print(log_msg)
with open(LOG_FILE, 'a') as f:
f.write(log_msg + "\n")
def wait_for_quota_reset():
"""Wait until quota resets"""
# Make now timezone-aware to match QUOTA_RESET_TIME
now = datetime.now(timezone.utc).astimezone()
wait_seconds = (QUOTA_RESET_TIME - now).total_seconds()
if wait_seconds > 0:
log(f"⏰ Waiting for quota reset at {QUOTA_RESET_TIME.strftime('%H:%M')}")
log(f" Sleeping for {int(wait_seconds/60)} minutes...")
time.sleep(wait_seconds + 10) # Add 10 sec buffer
log("✅ Quota should be reset now!")
else:
log("✅ Quota already reset!")
def get_access_token():
"""Get Google Cloud access token"""
import subprocess
result = subprocess.run(
['gcloud', 'auth', 'print-access-token'],
capture_output=True,
text=True
)
return result.stdout.strip()
def generate_image_vertex(prompt, output_path):
"""Generate image using Vertex AI Imagen 3.0"""
access_token = get_access_token()
url = f"https://{LOCATION}-aiplatform.googleapis.com/v1/projects/{PROJECT_ID}/locations/{LOCATION}/publishers/google/models/{MODEL}:predict"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
payload = {
"instances": [
{
"prompt": prompt
}
],
"parameters": {
"sampleCount": 1,
"aspectRatio": "1:1",
"safetyFilterLevel": "block_few",
"personGeneration": "allow_all"
}
}
try:
response = requests.post(url, headers=headers, json=payload, timeout=60)
response.raise_for_status()
result = response.json()
# Extract and save image
if "predictions" in result and len(result["predictions"]) > 0:
image_data = result["predictions"][0]["bytesBase64Encoded"]
image_bytes = base64.b64decode(image_data)
# Save to file
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, 'wb') as f:
f.write(image_bytes)
log(f" ✅ Saved: {output_path.name}")
return True
else:
log(f" ❌ No image in response")
return False
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
log(f" ⚠️ Quota exhausted again, waiting 5 minutes...")
time.sleep(300)
return False
else:
log(f" ❌ HTTP Error: {e}")
return False
except Exception as e:
log(f" ❌ Error: {str(e)}")
return False
def git_commit_checkpoint(message):
"""Create git commit checkpoint"""
import subprocess
try:
subprocess.run(['git', 'add', '-A'], cwd=PROJECT_ROOT, check=True)
subprocess.run(['git', 'commit', '-m', message], cwd=PROJECT_ROOT, check=True)
log(f" 📦 Git commit: {message}")
except Exception as e:
log(f" ⚠️ Git commit failed: {e}")
# ========================================
# MAIN GENERATION LOGIC
# ========================================
def generate_dino_valley_remaining():
"""Generate remaining Dino Valley assets"""
log("🦖 Starting Dino Valley completion...")
total = len(DINO_VALLEY_REMAINING["creatures"])
completed = 0
for asset in DINO_VALLEY_REMAINING["creatures"]:
name = asset["name"]
prompt_template = asset["prompt"]
# Use Style 33 for entities
prompt = prompt_template.replace("{STYLE}", STYLE_33_ENTITIES)
output_path = ASSET_ROOT / "biomes/dino_valley/creatures" / f"{name}.png"
# Skip if already exists
if output_path.exists():
log(f"⏭️ Skipping {name} (already exists)")
completed += 1
continue
log(f"🖼️ Generating {name} ({completed+1}/{total})")
success = generate_image_vertex(prompt, output_path)
if success:
completed += 1
# Checkpoint every 5 images
if completed % 5 == 0:
git_commit_checkpoint(f"🦖 Dino Valley auto: {completed}/{total} creatures")
# Rate limiting
time.sleep(DELAY_BETWEEN_IMAGES)
log(f"✅ Dino Valley complete! {completed}/{total} assets generated")
git_commit_checkpoint(f"🎉 DINO VALLEY COMPLETE - All {completed} creatures done!")
return completed
# ========================================
# MAIN EXECUTION
# ========================================
if __name__ == "__main__":
log("="*60)
log("🚀 OVERNIGHT AUTOMATION STARTED")
log("="*60)
# Wait for quota reset
wait_for_quota_reset()
# Generate remaining Dino Valley
dino_count = generate_dino_valley_remaining()
log("="*60)
log(f"✅ AUTOMATION COMPLETE!")
log(f"📊 Total generated: {dino_count} assets")
log(f"💰 Estimated cost: €{dino_count * 0.40:.2f}")
log("="*60)