#!/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 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""" now = datetime.now() 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)