GitHub - hbmartin/imagemine: 📸 → 🍌 → 🖼️ ... Re-imagine any photo into fantastical art. imagemine uses Claude to write a surrealist story about your photo, then generates a new image from that description using Gemini / Nano Banana.

GitHub

13 min read Original article ↗

PyPI Ruff ty CI codecov Ask DeepWiki

Transform any photo into something new. imagemine uses Claude to write a surrealist story about your photo, then generates a new image from that description using Gemini / Nano Banana.

Table of Contents

How it works

  1. Resize input image to max 1024px (preserving aspect ratio) and save to disk
  2. Send the resized image to Claude Sonnet via the Files API; generate a short surrealist story and image prompt
  3. Pick a random visual style from the built-in style library (or specify one with --style)
  4. Pass the story + style + resized image to Gemini to generate the re-imagined version
  5. Save all run metadata to a local SQLite database (~/.imagemine.db)

Requirements

Quick Start

uvx imagemine path/to/photo.jpg

The generated image is saved to the current directory. The terminal UI shows each pipeline step with live spinners, elapsed timers, and a final summary panel.

Installing

To use imagemine without invoking uvx, permanently install it as:

curl -LsSf uvx.sh/imagemine/install.sh | sh
imagemine path/to/photo.jpg

API keys

Keys are resolved in this order on each run:

  1. Database — stored in ~/.imagemine.db after first entry
  2. Environment variablesANTHROPIC_API_KEY, GEMINI_API_KEY
  3. Interactive prompt — if neither is found, you are prompted (input is masked) and the value is saved to the database for future runs

To set keys via environment:

export ANTHROPIC_API_KEY=sk-ant-...
export GEMINI_API_KEY=AI...

To set or update keys directly in the config database:

CLI flags

Flag Default Description
image_path Path to input image (omit to use --input-album or --input-dir)
--input-album DB / env macOS Photos album to pick a random input image from
--input-dir PATH DB / env Directory to pick a random input image from (works on any OS); never-used images are preferred
--output-dir . Directory to save the generated image
--desc-temp DB / 1.0 Sampling temperature for Claude description generation
--img-temp DB / 1.0 Sampling temperature for Gemini image generation
--story TEXT Background context prepended to the Claude prompt when generating the image description
--style PROMPT Use PROMPT as the style instead of a randomly selected one from the database
--fresh off Pick style randomly from the least-used styles (ignored when --style is given)
--choose-style off Interactively pick style(s) from a numbered table before running the pipeline
--dry-run off Stop after the describe and style steps and show the final prompt without generating an image
--reuse-description off Reuse the latest cached description for this input image and Claude model instead of calling Claude again
--from-run ID Reuse the description (and input image, unless one is given) from a previous run shown in --history
--count N 1 Run the full pipeline N times
--variations N 1 Generate N images from a single description (one Claude call, N Gemini calls)
--list-styles Show all styles in the database as a table and exit
--add-style Interactively add a new style to the database and exit
--remove-style Interactively select and remove styles from the database and exit
--export-styles PATH Write all styles to PATH as JSON and exit
--import-styles PATH Load styles from a JSON file created by --export-styles and exit
--aspect-ratio RATIO DB / env / 4:3 Aspect ratio for generated image (see supported ratios, e.g. 1:1, 3:4, 4:3, 9:16, 16:9)
--add-character-mapping Interactively add a character name mapping and exit
--remove-character-mapping Interactively remove character name mapping(s) and exit
--list-character-mappings Show all character name mappings and exit
--destination-album DB / env macOS Photos album to import the generated image into
--silent off Suppress Rich UI; only print the output file path(s)
--json off Output run results as JSON (suppresses Rich UI)
--config Interactively configure settings and exit
--history Show recent runs as a table and exit
--config-path ~/.imagemine.db Path to the SQLite database file
--launchd [MINUTES] Write a launchd plist to ~/Library/LaunchAgents/imagemine.plist that runs imagemine on the given interval; omit MINUTES to be prompted. Prints the launchctl command to activate it.
--print-completion {bash,zsh,tcsh} Print a shell completion script and exit

--input-album and --input-dir are mutually exclusive. When image_path or --from-run is provided, that explicit source takes precedence over configured INPUT_ALBUM and INPUT_DIR defaults. When picking from an album or a directory, photos that were never used on a previous run are preferred, so scheduled runs keep transforming fresh material.

Examples

# Basic usage
imagemine sunset.jpg

# Pick a random photo from a Photos album
imagemine --input-album "Camera Roll"

# Pick a random photo from a directory (works on Linux/Windows too)
imagemine --input-dir ~/Pictures/inbox

# Preview the prompt without paying for image generation
imagemine photo.jpg --dry-run

# Iterate on styles without a new Claude call
imagemine photo.jpg --reuse-description --style "Vaporwave"

# Re-run a previous run's story with a different temperature
imagemine --from-run 42 --img-temp 0.6

# Generate 3 images from one description (one Claude call, 3 Gemini calls)
imagemine photo.jpg --variations 3

# Run the whole pipeline 5 times
imagemine --input-album "Camera Roll" --count 5

# Custom output directory
imagemine photo.jpg --output-dir ~/Desktop/imagemine-out

# Tune creativity
imagemine photo.jpg --desc-temp 1.5 --img-temp 0.8

# Use a custom style prompt instead of a random one
imagemine photo.jpg --style "Ukiyo-e woodblock print, bold outlines, flat color"

# Generate with a specific aspect ratio
# Must be one of: https://ai.google.dev/gemini-api/docs/image-generation#aspect_ratios_and_image_size
imagemine photo.jpg --aspect-ratio 16:9

# List all styles in the database
imagemine --list-styles

# Add a new style interactively
imagemine --add-style

# Pick style(s) interactively from a numbered table
imagemine photo.jpg --choose-style

# Silent mode — only prints the output file path
imagemine photo.jpg --silent

# JSON mode — output run results as structured JSON
imagemine photo.jpg --json

# Save an SVG of the terminal session alongside the generated image
imagemine photo.jpg --session-svg

# Show recent run history with timing sparklines
imagemine --history

# Configure API keys and settings interactively
imagemine --config

# Use a custom database location
imagemine photo.jpg --config-path ~/work/imagemine.db

# Configure settings into a custom database
imagemine --config --config-path ~/work/imagemine.db

Styles

Each run applies a randomly selected visual style from a built-in library of 35+ styles. The style name and description are appended to the image prompt sent to Gemini.

Example styles: Watercolor, 8-Bit Pixel Art, Ukiyo-e Woodblock, Neon Noir, Tarot Card, Vaporwave, Glitch Art, Renaissance Painting, and more.

Choosing styles interactively

imagemine photo.jpg --choose-style

Displays a numbered table of all styles. Enter one number (e.g. 3) to use that style, or a comma-separated list (e.g. 1,5,12) to blend the selected styles into a single prompt. Each selected style's usage count is incremented.

Using a custom style prompt

Use --style to pass a free-form style prompt directly instead of picking one at random from the database. imagemine will describe the photo, append the style text, and generate the image.

imagemine photo.jpg --style "Risograph print, grainy ink, limited overlapping spot colors"

Listing styles

Prints a table of all styles in the database with their description and usage count.

Adding a style

Prompts for a name and a description/prompt, then saves the new style to the database. It will be included in random selection on future runs.

Removing a style

Displays a numbered table of all styles. Enter one or more numbers (e.g. 1 or 1,3,5), review the confirmation prompt, and confirm to delete.

Sharing style libraries

imagemine --export-styles styles.json
imagemine --import-styles styles.json

--export-styles writes every style as a JSON list of {"name": ..., "description": ...} objects. --import-styles upserts each entry by name (existing usage counts are preserved), so you can back up your library or share it with others.

Character Mappings

When imagemine picks a photo from a macOS Photos album, it reads any face-detection names associated with the photo. Character mappings let you rename these before they reach the AI prompt — for example, mapping "John Smith" to "Captain America".

Adding a mapping

imagemine --add-character-mapping
# Input name (from Photos): John Smith
# Mapped name (for prompt): Captain America

Listing mappings

imagemine --list-character-mappings

Removing mappings

imagemine --remove-character-mapping

Displays a numbered list of mappings. Enter one or more numbers to remove, then confirm.

Configuration

Run the interactive config wizard to set or update any value:

The wizard walks through each key in order, pre-populates non-secret fields with their current values, and masks API key input. Leaving a field blank keeps the existing value (or skips it if unset).

Key Description
ANTHROPIC_API_KEY Anthropic API key (checked before env var)
GEMINI_API_KEY Google Gemini API key (checked before env var)
DEFAULT_DESC_TEMP Default sampling temperature for description generation (e.g. 1.2)
DEFAULT_IMG_TEMP Default sampling temperature for image generation (e.g. 0.8)
CLAUDE_MODEL Claude model to use for description (default: claude-sonnet-4-6)
GEMINI_MODEL Gemini model to use for image generation (default: gemini-3-pro-image-preview)
INPUT_ALBUM macOS Photos album to pick a random input image from
INPUT_DIR Directory to pick a random input image from
DESTINATION_ALBUM macOS Photos album name to import generated images into
ASPECT_RATIO Aspect ratio for generated images (default: 4:3); must be a ratio listed at the Gemini docs

Cutting the description cost

The description step is a short creative-writing task, so a smaller Claude model works well. Set CLAUDE_MODEL to claude-haiku-4-5 (roughly a third of the per-token price of the default Sonnet model) to make scheduled runs cheaper:

CLAUDE_MODEL=claude-haiku-4-5 imagemine photo.jpg

Each run records the Claude token counts, and --history shows the estimated description cost per run.

Apple TV screensaver

You can use imagemine to generate a living screensaver on Apple TV by continuously transforming photos from one album and depositing the results into another.

Setup

  1. In macOS Photos, create two albums — one for source photos (e.g. Camera Roll) and one for the generated output (e.g. imagemine). Make the output album a Shared Album so Apple TV can see it.

  2. Configure imagemine to read from the source album and write to the output album:

  1. Run imagemine on a schedule via launchd to keep the output album growing. Use the --launchd flag to write the agent configuration automatically:

Pass a number of minutes as the argument. If you omit it, imagemine will prompt you:

imagemine --launchd
# Run interval (minutes): 30

Both commands write ~/Library/LaunchAgents/imagemine.plist and print the launchctl command to activate it. Pass --config-path to use a non-default database location:

imagemine --launchd 30 --config-path ~/work/imagemine.db

Then follow the printed instructions to load the agent:

launchctl load ~/Library/LaunchAgents/imagemine.plist

Setting the screensaver on Apple TV

  1. Open the Photos app on Apple TV 4K.
  2. Navigate to Shared Albums or Albums at the top of the screen.
  3. Select the output album (imagemine), then select Set as Screen Saver and confirm.

New images added to the shared album will appear in the screensaver automatically. Album photos that were never used before are preferred when picking, so the screensaver keeps getting fresh transformations instead of re-processing the same shots.

Scheduled runs on Linux and Windows

--input-dir gives non-macOS users the same "scheduled surprise" workflow: point it at a directory of photos and pair it with cron or a systemd timer.

# Configure once
imagemine --config   # set API keys and INPUT_DIR

# cron: one image every 30 minutes
*/30 * * * * imagemine --output-dir ~/Pictures/imagemine-out --silent >> ~/imagemine.log 2>&1

Or with a systemd user timer (~/.config/systemd/user/imagemine.timer + .service):

# imagemine.service
[Service]
ExecStart=%h/.local/bin/imagemine --output-dir %h/Pictures/imagemine-out --silent

# imagemine.timer
[Timer]
OnUnitActiveSec=30min
OnBootSec=5min

[Install]
WantedBy=timers.target
systemctl --user enable --now imagemine.timer

Use imagemine --history to check on scheduled runs: each run records a success/error status (with the error message) alongside timing and estimated cost.

Shell completions

Generate a completion script for your shell with --print-completion:

imagemine --print-completion bash | sudo tee /etc/bash_completion.d/imagemine
imagemine --print-completion zsh > ~/.zsh/completions/_imagemine

Development

Setup

git clone https://github.com/hbmartin/imagemine
cd imagemine
uv sync --dev

To run from the local source:

uv run imagemine path/to/photo.jpg

Run the linters, type checker, and tests:

uv run ruff check src --fix
uv run pyrefly check src
uv run ty check src
uv run pytest tests/

Database

Every run is recorded in ~/.imagemine.db by default. Use --config-path to specify a different location. The runs table tracks:

Column Description
started_at UTC timestamp when the run began
input_file_path Absolute path to the source image
resized_file_path Path to the temporary resized JPEG (deleted after generation)
generated_description Story + image prompt from Claude
description_model_name Claude model used
desc_temp Temperature used for description generation
desc_gen_ms Description generation time in milliseconds
output_image_path Path to the generated image
image_model_name Gemini model used
img_temp Temperature used for image generation
img_gen_ms Image generation time in milliseconds
input_album_photo_id Photos item ID when input was selected from an album
style Visual style applied to the image prompt
status running, success, or error
error Error message when the run failed
desc_input_tokens Claude input tokens used for description generation
desc_output_tokens Claude output tokens used for description generation

The database uses SQLite WAL mode with a 5-second busy timeout, so an overlapping scheduled (launchd/cron) run and a manual run don't fail with "database is locked".

A new description is generated from Claude on every run unless --reuse-description or --from-run ID is given.

Using imagemine as a library

The pipeline is importable: run_pipeline raises imagemine.PipelineError subclasses (InputError, ResizeError, DescriptionError, GenerationError) instead of exiting the process, so failures can be handled programmatically.

import pathlib
from rich.console import Console

from imagemine import (
    NullProgressReporter,
    PipelineConfig,
    PipelineError,
    init_db,
    run_pipeline,
)

conn = init_db(pathlib.Path.home() / ".imagemine.db")
config = PipelineConfig(
    output_dir=pathlib.Path("out"),
    image_path="photo.jpg",
    anthropic_api_key="sk-ant-...",
    gemini_api_key="AI...",
)
try:
    result = run_pipeline(conn, Console(), config, progress=NullProgressReporter())
except PipelineError as e:
    print(f"failed: {e}")

Terminal UI

imagemine uses Rich for its terminal output:

  • Labeled section rules divide the pipeline into phases: Resize → Describe → Style → Generate
  • Live spinners with elapsed timers show progress during the two API calls — a moon spinner for Claude description generation and a smiley spinner for Gemini image generation
  • Description panel renders the generated story as formatted Markdown inside a cyan-bordered panel
  • Style tree shows the randomly selected style with its full description
  • Summary panel displays source file, style, per-step timing, total wall time, and output path
  • --history table lists recent runs with success/error status, per-step timing, total time, a proportional sparkline bar, and the estimated Claude cost
  • --session-svg saves a styled SVG of the entire terminal session alongside the generated image
  • Error tracebacks are rendered with Rich syntax highlighting when an API call fails

Legal

Copyright 2026 Harold Martin. Licensed under the Apache License, Version 2.0.

Apple, Apple TV, and Photos are trademarks of Apple Inc., registered in the U.S. and other countries.

Claude and Anthropic are trademarks of Anthropic, PBC.

Google and Gemini are trademarks of Google LLC.

This project is not affiliated with, endorsed by, or sponsored by Apple Inc., Anthropic, PBC, or Google LLC.