#!/usr/bin/env python3 """Generate combo images from all kq4-decompile visual images that have captions.""" import subprocess import sys import os import re import argparse from pathlib import Path DEFAULT_CAPTION_TEXT = "oil painting style" def find_captions_for_image(image_path: Path) -> list[tuple[Path, str, int]]: """Find all caption files for a visual image. Looks for caption files in asset-work/kq4_XXX_room_name/ directory. Returns list of (caption_path, caption_text, caption_number) tuples. Uses a set to avoid duplicates when multiple room folders match. Args: image_path: Path to the visual image Returns: List of tuples with (caption_file_path, caption_text, caption_number) """ image_basename = os.path.basename(image_path) room_number = image_basename.replace("pic_", "").split("_")[0] room_folder_pattern = f"kq4_{room_number}_*" script_dir = Path(__file__).parent.resolve() captions_set = set() captions = [] for room_dir in script_dir.glob(room_folder_pattern): if room_dir.is_dir(): for caption_file in sorted(room_dir.glob("caption_*.txt")): match = re.match(r"caption_(\d+)\.txt", caption_file.name) if match: caption_num = int(match.group(1)) caption_key = (caption_num, caption_file.name) if caption_key not in captions_set: captions_set.add(caption_key) with open(caption_file, "r") as f: caption_text = f.read().strip() captions.append((caption_file, caption_text, caption_num)) return sorted(captions, key=lambda x: x[2]) if captions else [(None, DEFAULT_CAPTION_TEXT, 0)] def find_caption_for_image(image_path: Path) -> tuple[Path | None, str]: """Find first caption file for a visual image. Args: image_path: Path to the visual image Returns: Tuple of (caption_file_path or None, caption_text or default) """ captions = find_captions_for_image(image_path) if captions and captions[0][0]: return captions[0][0], captions[0][1] return None, DEFAULT_CAPTION_TEXT def main(): parser = argparse.ArgumentParser( description="Generate combo images from all kq4-decompile visual images with captions" ) parser.add_argument( "--dry-run", action="store_true", help="Dry run mode - validate without generating", ) args = parser.parse_args() script_dir = Path(__file__).parent.resolve() generator = script_dir / "generate_multiple_combo.py" dry_run = args.dry_run if not generator.exists(): print(f"Error: generate_multiple_combo.py not found in {script_dir}") sys.exit(1) print("Finding all kq4-decompile visual images...") visual_images = list(Path("kq4-sierra-decompile/rooms").rglob("pic_*_visual.png")) total = len(visual_images) if total == 0: print("No kq4-decompile visual images found!") sys.exit(0) print(f"Found {total} visual image(s)") all_images_to_process = [] for image_path in visual_images: captions = find_captions_for_image(image_path) for caption_path, caption_text, caption_num in captions: all_images_to_process.append((image_path, caption_text, caption_path, caption_num)) total_captions = len(all_images_to_process) print(f"Found {total_captions} captions to process ({len(visual_images)} rooms)") if dry_run: print("\nDRY RUN MODE - Validating all visual images") counter = 0 failed = 0 total_existing = 0 total_created = 0 total_needed = 0 variations_per_image = 1 for image_path, caption_text, caption_path, caption_num in all_images_to_process: counter += 1 caption_info = f"[caption_{caption_num}]" if caption_path else "[default]" cmd = [ "python3", str(generator), str(image_path), caption_text, "--count", str(variations_per_image), "--caption-num", str(caption_num), ] if dry_run: cmd.append("--dry-run") try: result = subprocess.run(cmd, capture_output=True, text=True, check=True) print(result.stdout) existing_match = re.search( r"[Ff]ound (\d+) existing variations?", result.stdout ) created_match = re.search(r"Created (\d+) new image", result.stdout) if existing_match: existing = int(existing_match.group(1)) total_existing += existing needed = max(0, variations_per_image - existing) total_needed += needed if created_match: total_created += int(created_match.group(1)) status = "✓" if result.returncode == 0 else "✗" print(f"[{counter}/{total_captions}] {status} {image_path.name} {caption_info}") except subprocess.CalledProcessError as e: failed += 1 print(f"[{counter}/{total_captions}] ✗ {image_path.name} {caption_info} - FAILED") print() print("=" * 50) if dry_run: print("DRY RUN COMPLETE") print("=" * 50) print(f"Rooms found: {total}") print(f"Captions to process: {total_captions}") print(f"Images already existing: {total_existing}") print(f"Images would be created: {total_needed}") print(f"Total when complete: {total_existing + total_needed}") if failed == 0: print(f"✓ All captions validated successfully!") else: print(f"✗ {failed} of {total_captions} captions failed validation") sys.exit(1) else: print("GENERATION COMPLETE") print("=" * 50) print(f"Rooms processed: {total}") print(f"Captions processed: {total_captions}") print(f"Images already existing: {total_existing}") print(f"Images newly created: {total_created}") print(f"Total images now: {total_existing + total_created}") if failed > 0: print(f"Failed: {failed}") if __name__ == "__main__": main()