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%)
254 lines
9.1 KiB
Python
Executable File
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)
|