#!/usr/bin/env python3 """ Smart Asset Organization System Reorganizes all assets into logical folder structure with smart naming """ import os import shutil from pathlib import Path from typing import Dict, List import json # Base directories PROJECT_ROOT = Path(__file__).parent.parent ASSETS_DIR = PROJECT_ROOT / "assets" SLIKE_DIR = ASSETS_DIR / "slike 🟢" STYLE32_DIR = ASSETS_DIR / "images" / "STYLE_32_SESSION_JAN_04" # Target organization structure TARGET_STRUCTURE = { "liki": { # Characters "gronk": ["gronk", "grok"], "kai": ["kai"], "ana": ["ana"], "zombiji": ["zombie"], "npcs": ["npc", "farmer", "priest", "blacksmith", "merchant", "mayor", "lawyer"] }, "biomi": { # Biomes "desert": ["desert", "sand"], "gore": ["mountain", "rock", "boulder"], "džungla": ["jungle", "tropical"], "močvirje": ["swamp", "mud"], "arktika": ["arctic", "ice", "snow"] }, "zgradbe": { # Buildings "hiše": ["house", "home", "farmhouse"], "javne": ["church", "town_hall", "clinic", "bakery", "tavern"], "kmetijske": ["barn", "silo", "greenhouse", "windmill"], "delavnice": ["workshop", "blacksmith", "laboratory", "mint"] }, "objekti": { # Props "pohištvo": ["chair", "table", "bed", "sofa", "wardrobe"], "shranjevanje": ["chest", "barrel", "crate", "shelf"], "razsvetljava": ["lantern", "torch", "lamp"], "dekoracije": ["statue", "fountain", "monument", "grave"] }, "narava": { # Nature "rastline": ["plant", "tree", "bush", "flower", "grass"], "pridelki": ["crop", "wheat", "carrot", "tomato", "potato", "cabbage"], "živali": ["cow", "sheep", "chicken", "pig", "wolf", "rabbit"] }, "oprema": { # Equipment "orožje": ["sword", "axe", "bow", "spear", "dagger", "mace", "weapon"], "orodja": ["pickaxe", "shovel", "hoe", "hammer", "saw", "tool"], "zaščita": ["armor", "shield", "helmet"] }, "vmesnik": { # UI "gumbi": ["button"], "ikone": ["icon"], "vrstice": ["bar"], "okna": ["panel", "window", "dialogue"] }, "teren": ["terrain"], # Terrain tiles "notranjost": ["interior"], # Interior objects "učinki": ["effect", "particle", "spark", "glow"], # VFX "kreature_mutanti": ["mutant", "radioactive", "three_eyed", "two_headed"] # Mutants } def detect_category_and_subfolder(filename: str) -> tuple: """ Detect the appropriate category and subfolder for a file based on its name Returns: (category, subfolder) or (category, None) if no subfolder """ lower_name = filename.lower() # Check nested categories first for category, subfolders in TARGET_STRUCTURE.items(): if isinstance(subfolders, dict): for subfolder, keywords in subfolders.items(): if any(keyword in lower_name for keyword in keywords): return (category, subfolder) else: # Simple list of keywords if any(keyword in lower_name for keyword in subfolders): return (category, None) return ("ostalo", None) # Default: "other" def generate_smart_name(filepath: Path, category: str, subfolder: str = None) -> str: """ Generate a smart, descriptive name for an asset Format: {category}_{content_description}_style32.png (if from Style 32) """ filename = filepath.stem extension = filepath.suffix # Check if it's from Style 32 is_style32 = "STYLE_32" in str(filepath) or "style32" in filename.lower() # Clean up existing name clean_name = filename.lower() clean_name = clean_name.replace("_style32", "").replace("style32", "") clean_name = clean_name.replace("interior_", "").replace("terrain_", "") # Remove timestamp patterns (e.g., _1767549405033) import re clean_name = re.sub(r'_\d{13}', '', clean_name) # Build new name parts = [] if category != "ostalo": parts.append(category) if subfolder: parts.append(subfolder) parts.append(clean_name) if is_style32: parts.append("style32") new_name = "_".join(parts) + extension return new_name def organize_assets(dry_run: bool = True): """ Organize all assets into the target structure Args: dry_run: If True, only print what would be done without moving files """ print("🗂️ Starting Asset Organization...") print(f"📁 Source directories:") print(f" - {SLIKE_DIR}") print(f" - {STYLE32_DIR}") print() moved_count = 0 renamed_count = 0 skipped_count = 0 # Create manifest for tracking manifest = { "total_processed": 0, "moved": 0, "renamed": 0, "skipped": 0, "assets": [] } # Process all PNG files in both directories all_images = [] all_images.extend(SLIKE_DIR.rglob("*.png")) all_images.extend(STYLE32_DIR.glob("*.png")) print(f"📊 Found {len(all_images)} images to process\n") for img_path in all_images: category, subfolder = detect_category_and_subfolder(img_path.name) new_name = generate_smart_name(img_path, category, subfolder) # Determine target directory target_dir = SLIKE_DIR / category if subfolder: target_dir = target_dir / subfolder target_path = target_dir / new_name # Check if file already exists at target if target_path.exists() and target_path != img_path: print(f"⚠️ SKIP (exists): {img_path.name}") skipped_count += 1 continue # Create directory if needed if not dry_run: target_dir.mkdir(parents=True, exist_ok=True) # Show what we're doing if img_path != target_path: action = "MOVE" if img_path.parent != target_path.parent else "RENAME" print(f"✅ {action}: {img_path.name}") print(f" → {target_path.relative_to(ASSETS_DIR)}") print() if action == "MOVE": moved_count += 1 else: renamed_count += 1 # Actually move/rename if not dry run if not dry_run: shutil.move(str(img_path), str(target_path)) # Add to manifest manifest["assets"].append({ "original": str(img_path.relative_to(PROJECT_ROOT)), "new": str(target_path.relative_to(PROJECT_ROOT)), "category": category, "subfolder": subfolder, "action": action.lower() }) # Update manifest totals manifest["total_processed"] = len(all_images) manifest["moved"] = moved_count manifest["renamed"] = renamed_count manifest["skipped"] = skipped_count # Save manifest if not dry_run: manifest_path = PROJECT_ROOT / "docs" / "ASSET_ORGANIZATION_MANIFEST.json" with open(manifest_path, 'w', encoding='utf-8') as f: json.dump(manifest, f, indent=2, ensure_ascii=False) print(f"\n📄 Manifest saved to: {manifest_path}") # Print summary print("\n" + "="*60) print("📊 ORGANIZATION SUMMARY") print("="*60) print(f"Total Processed: {len(all_images)}") print(f"Moved: {moved_count}") print(f"Renamed: {renamed_count}") print(f"Skipped: {skipped_count}") if dry_run: print("\n⚠️ DRY RUN MODE - No files were actually moved") print(" Run with --execute to apply changes") else: print("\n✅ Organization complete!") def print_category_preview(): """Print a preview of how files would be categorized""" print("\n📂 CATEGORY STRUCTURE PREVIEW:") print("="*60) for category, subfolders in TARGET_STRUCTURE.items(): if isinstance(subfolders, dict): print(f"\n📁 {category.upper()}/") for subfolder in subfolders.keys(): print(f" └── {subfolder}/") else: print(f"\n📁 {category.upper()}/") if __name__ == "__main__": import sys print("🎨 DolinaSmrti - Smart Asset Organization System") print("="*60) # Check if --execute flag is provided execute = "--execute" in sys.argv or "-e" in sys.argv preview_only = "--preview" in sys.argv or "-p" in sys.argv if preview_only: print_category_preview() else: print(f"\nMode: {'EXECUTE' if execute else 'DRY RUN'}") print() organize_assets(dry_run=not execute) if not execute: print("\n💡 TIP: Run with --execute to actually move files") print(" Run with --preview to see category structure")