#!/usr/bin/env python3 """ CHARACTER ANIMATION GENERATOR - WORKING VERSION Uses direct REST API instead of deprecated SDK Generates animations for Kai, Ana, Gronk Date: 1.1.2026 """ import os import time import json import base64 from pathlib import Path import requests from PIL import Image import io # ==================== CONFIGURATION ==================== API_KEY = os.getenv('GEMINI_API_KEY') or os.getenv('GOOGLE_API_KEY') if not API_KEY: print("❌ ERROR: API key not found!") print("Please set: export GEMINI_API_KEY='your-key-here'") exit(1) print(f"✅ API Key found: {API_KEY[:10]}...") OUTPUT_DIR = Path("assets/slike") DELAY_BETWEEN_CALLS = 15 # seconds API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent" # ==================== STYLE CONSTANTS ==================== STYLE_BASE = """Dark Hand-Drawn 2D Stylized Indie Game Art. CRITICAL: Thick bold black outlines (3-4px), exaggerated proportions, warped perspective. High-contrast noir elements. Centered subject with 10px transparent margin. 32-bit PNG with alpha channel. Clean background (pure transparency).""" KAI_BASE = """Character: KAI MARKOVIĆ (Age 17, Male Protagonist) - Green & Pink dreadlocks (distinctive!) - Tactical survivor outfit (torn, weathered) - Lean athletic build, determined expression""" ANA_BASE = """Character: ANA MARKOVIĆ (Age 17, Female Twin) - Pink dreadlocks (all pink, matches Kai's style) - Explorer vest, cargo pants - Slender athletic build, intelligent caring expression""" GRONK_BASE = """Character: GRONK (Zen Troll Companion) - Large green troll, 7ft tall - Pink mohawk hair, multiple piercings - Vape pen accessory (signature!) - Baggy pants, no shirt, chill relaxed expression""" # ==================== SIMPLIFIED ANIMATION SET ==================== # Start with essentials only to test the system KAI_ANIMATIONS = { "walk_south": {"frames": 4, "desc": "walking toward camera, front view"}, "walk_east": {"frames": 4, "desc": "walking right, side view"}, "idle_front": {"frames": 4, "desc": "idle standing, front view, breathing"}, "attack_south": {"frames": 4, "desc": "sword swing downward"}, } ANA_ANIMATIONS = { "walk_south": {"frames": 4, "desc": "walking toward camera, front view"}, "walk_east": {"frames": 4, "desc": "walking right, side view"}, "idle_front": {"frames": 4, "desc": "idle standing, front view"}, "research_front": {"frames": 4, "desc": "writing in notebook"}, } GRONK_ANIMATIONS = { "walk_south": {"frames": 4, "desc": "heavy lumbering walk, front view"}, "walk_east": {"frames": 4, "desc": "heavy walk right, side view"}, "idle_front": {"frames": 4, "desc": "idle, chill stance, front view"}, "vape_front": {"frames": 6, "desc": "vaping, exhaling smoke cloud"}, } # ==================== GENERATION FUNCTIONS ==================== def generate_image_rest_api(prompt): """Generate image using REST API""" payload = { "contents": [{ "parts": [{ "text": prompt }] }], "generationConfig": { "temperature": 0.4, "topK": 32, "topP": 1, "maxOutputTokens": 4096, } } headers = { "Content-Type": "application/json" } url = f"{API_URL}?key={API_KEY}" try: response = requests.post(url, headers=headers, json=payload, timeout=60) response.raise_for_status() data = response.json() # Extract image from response if 'candidates' in data and len(data['candidates']) > 0: candidate = data['candidates'][0] if 'content' in candidate and 'parts' in candidate['content']: for part in candidate['content']['parts']: if 'inlineData' in part: image_b64 = part['inlineData']['data'] image_bytes = base64.b64decode(image_b64) image = Image.open(io.BytesIO(image_bytes)) return image print(f" ❌ No image in response") return None except requests.exceptions.RequestException as e: print(f" ❌ API Error: {e}") return None except Exception as e: print(f" ❌ Error: {e}") return None def generate_animation_frame(character_name, character_base, animation_name, frame_num, animation_desc): """Generate a single animation frame""" prompt = f"""{STYLE_BASE} {character_base} ANIMATION: {animation_name} - Frame {frame_num}/4 Description: {animation_desc} Frame timing: - Frame 1: Start pose - Frame 2: Mid-motion - Frame 3: Peak/impact - Frame 4: Follow-through Generate this specific frame showing clear progression. Maintain character consistency.""" print(f" 🎨 {character_name} - {animation_name} - Frame {frame_num}...") image = generate_image_rest_api(prompt) return image def generate_character_animations(character_name, character_base, animations_dict): """Generate all animations for a character""" char_dir = OUTPUT_DIR / character_name.lower() char_dir.mkdir(parents=True, exist_ok=True) total_frames = sum(anim["frames"] for anim in animations_dict.values()) current_frame = 0 print(f"\n{'='*60}") print(f"🎬 GENERATING {character_name.upper()}") print(f"{'='*60}") print(f"Animations: {len(animations_dict)}") print(f"Total frames: {total_frames}") print(f"Output: {char_dir}\n") for anim_name, anim_data in animations_dict.items(): frames_count = anim_data["frames"] desc = anim_data["desc"] print(f"\n📹 {anim_name} ({frames_count} frames)") for frame_num in range(1, frames_count + 1): current_frame += 1 image = generate_animation_frame( character_name, character_base, anim_name, frame_num, desc ) if image: # Save original filename = f"{character_name.lower()}_{anim_name}_frame{frame_num}_1024x1024.png" filepath = char_dir / filename image.save(filepath, "PNG") print(f" ✅ Saved: {filename}") # Create preview preview = image.resize((256, 256), Image.Resampling.LANCZOS) preview_filename = f"{character_name.lower()}_{anim_name}_frame{frame_num}_preview_256x256.png" preview_filepath = char_dir / preview_filename preview.save(preview_filepath, "PNG") print(f" ✅ Preview: {preview_filename}") # Progress progress = (current_frame / total_frames) * 100 print(f" 📊 Progress: {current_frame}/{total_frames} ({progress:.1f}%)") # Rate limiting if current_frame < total_frames: print(f" ⏳ Waiting {DELAY_BETWEEN_CALLS}s...") time.sleep(DELAY_BETWEEN_CALLS) print(f"\n✅ {character_name.upper()} COMPLETE!") print(f"Generated: {total_frames} frames ({total_frames * 2} files)\n") return total_frames # ==================== MAIN ==================== def main(): print("=" * 80) print("🎮 DOLINASMRTI - CHARACTER ANIMATION GENERATOR") print("=" * 80) print("Date: 2026-01- 01") print("Mode: ESSENTIAL ANIMATIONS TEST") print(f"API Delay: {DELAY_BETWEEN_CALLS}s\n") start_time = time.time() total_generated = 0 # Generate essentials for each character kai_frames = generate_character_animations("Kai", KAI_BASE, KAI_ANIMATIONS) total_generated += kai_frames ana_frames = generate_character_animations("Ana", ANA_BASE, ANA_ANIMATIONS) total_generated += ana_frames gronk_frames = generate_character_animations("Gronk", GRONK_BASE, GRONK_ANIMATIONS) total_generated += gronk_frames # Summary elapsed = time.time() - start_time hours = int(elapsed // 3600) minutes = int((elapsed % 3600) // 60) print("\n" + "=" * 80) print("🎆 GENERATION COMPLETE!") print("=" * 80) print(f"Total frames: {total_generated}") print(f"Total files: {total_generated * 2}") print(f"Time: {hours}h {minutes}min") print(f"\nBreakdown:") print(f" - Kai: {kai_frames} frames") print(f" - Ana: {ana_frames} frames") print(f" - Gronk: {gronk_frames} frames") print("\n✅ Ready for game integration!") print("\n💡 View in Chrome: open preview_animations.html") if __name__ == "__main__": main()