From 92670a668750ba1c732ac7a9f868d279a5485487 Mon Sep 17 00:00:00 2001 From: beo3000 Date: Mon, 9 Feb 2026 15:58:26 +0100 Subject: [PATCH] feat: gemini-image skill with Python API scripts Replace CLI-based approach with Gemini Python API (google-genai). Scripts: generate_image.py, edit_image.py. Requires GEMINI_API_KEY. Co-Authored-By: Claude Opus 4.6 --- home-claude/skills/gemini-image/SKILL.md | 33 ++++++- .../skills/gemini-image/requirements.txt | 2 + .../skills/gemini-image/scripts/edit_image.py | 98 +++++++++++++++++++ .../gemini-image/scripts/generate_image.py | 91 +++++++++++++++++ 4 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 home-claude/skills/gemini-image/requirements.txt create mode 100644 home-claude/skills/gemini-image/scripts/edit_image.py create mode 100644 home-claude/skills/gemini-image/scripts/generate_image.py diff --git a/home-claude/skills/gemini-image/SKILL.md b/home-claude/skills/gemini-image/SKILL.md index 4313095..cacc627 100644 --- a/home-claude/skills/gemini-image/SKILL.md +++ b/home-claude/skills/gemini-image/SKILL.md @@ -1,8 +1,31 @@ --- name: gemini-image -description: Use when user asks to generate, create, or produce images. +description: Use when user asks to generate, create, edit, or produce images. --- -Generate images via Gemini CLI. -1. `gemini -p "generate an image of: " -o ` -2. Formats: PNG, JPG, WEBP -3. Ask for output path if not specified +# Gemini Image Generation + +Uses Gemini Python API via scripts in this skill's `scripts/` directory. + +## Setup (once) +```bash +pip install -r ~/.claude/skills/gemini-image/requirements.txt +``` +Requires `GEMINI_API_KEY` environment variable. + +## Generate image from text +```bash +python ~/.claude/skills/gemini-image/scripts/generate_image.py "" +``` +Options: `--model gemini-3-pro-image-preview` (higher quality), `--aspect 16:9`, `--size 2K` + +## Edit existing image +```bash +python ~/.claude/skills/gemini-image/scripts/edit_image.py "" +``` + +## Important +- Output format is **JPEG by default** — always use `.jpg` extension +- Models: `gemini-2.5-flash-image` (default, fast), `gemini-3-pro-image-preview` (higher quality) +- Aspect ratios: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 +- Sizes: 1K (default), 2K, 4K (4K only with pro model) +- Ask user for output path if not specified diff --git a/home-claude/skills/gemini-image/requirements.txt b/home-claude/skills/gemini-image/requirements.txt new file mode 100644 index 0000000..9b5d233 --- /dev/null +++ b/home-claude/skills/gemini-image/requirements.txt @@ -0,0 +1,2 @@ +google-genai>=1.0.0 +Pillow>=10.0.0 diff --git a/home-claude/skills/gemini-image/scripts/edit_image.py b/home-claude/skills/gemini-image/scripts/edit_image.py new file mode 100644 index 0000000..ae252d2 --- /dev/null +++ b/home-claude/skills/gemini-image/scripts/edit_image.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Edit existing images using Gemini API. + +Usage: + python edit_image.py input.jpg "edit instruction" output.jpg [--model MODEL] + +Environment: + GEMINI_API_KEY - Required API key +""" + +import argparse +import os +import sys + +from PIL import Image +from google import genai +from google.genai import types + + +def edit_image( + input_path: str, + instruction: str, + output_path: str, + model: str = "gemini-2.5-flash-image", + aspect_ratio: str | None = None, + image_size: str | None = None, +) -> str | None: + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise EnvironmentError("GEMINI_API_KEY environment variable not set") + + if not os.path.exists(input_path): + raise FileNotFoundError(f"Input image not found: {input_path}") + + client = genai.Client(api_key=api_key) + input_image = Image.open(input_path) + + config_kwargs = {"response_modalities": ["TEXT", "IMAGE"]} + + image_config_kwargs = {} + if aspect_ratio: + image_config_kwargs["aspect_ratio"] = aspect_ratio + if image_size: + image_config_kwargs["image_size"] = image_size + + if image_config_kwargs: + config_kwargs["image_config"] = types.ImageConfig(**image_config_kwargs) + + config = types.GenerateContentConfig(**config_kwargs) + + response = client.models.generate_content( + model=model, + contents=[instruction, input_image], + config=config, + ) + + text_response = None + image_saved = False + + for part in response.parts: + if part.text is not None: + text_response = part.text + elif part.inline_data is not None: + image = part.as_image() + image.save(output_path) + image_saved = True + + if not image_saved: + raise RuntimeError("No image was generated.") + + return text_response + + +def main(): + parser = argparse.ArgumentParser(description="Edit images using Gemini API") + parser.add_argument("input", help="Input image path") + parser.add_argument("instruction", help="Edit instruction") + parser.add_argument("output", help="Output file path (.jpg recommended)") + parser.add_argument("--model", "-m", default="gemini-2.5-flash-image", + choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"]) + parser.add_argument("--aspect", "-a", + choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]) + parser.add_argument("--size", "-s", choices=["1K", "2K", "4K"]) + + args = parser.parse_args() + + try: + text = edit_image(args.input, args.instruction, args.output, args.model, args.aspect, args.size) + print(f"Edited image saved to: {args.output}") + if text: + print(f"Model response: {text}") + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/home-claude/skills/gemini-image/scripts/generate_image.py b/home-claude/skills/gemini-image/scripts/generate_image.py new file mode 100644 index 0000000..396c616 --- /dev/null +++ b/home-claude/skills/gemini-image/scripts/generate_image.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Generate images from text prompts using Gemini API. + +Usage: + python generate_image.py "prompt" output.jpg [--model MODEL] [--aspect RATIO] [--size SIZE] + +Environment: + GEMINI_API_KEY - Required API key +""" + +import argparse +import os +import sys + +from google import genai +from google.genai import types + + +def generate_image( + prompt: str, + output_path: str, + model: str = "gemini-2.5-flash-image", + aspect_ratio: str | None = None, + image_size: str | None = None, +) -> str | None: + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise EnvironmentError("GEMINI_API_KEY environment variable not set") + + client = genai.Client(api_key=api_key) + + config_kwargs = {"response_modalities": ["TEXT", "IMAGE"]} + + image_config_kwargs = {} + if aspect_ratio: + image_config_kwargs["aspect_ratio"] = aspect_ratio + if image_size: + image_config_kwargs["image_size"] = image_size + + if image_config_kwargs: + config_kwargs["image_config"] = types.ImageConfig(**image_config_kwargs) + + config = types.GenerateContentConfig(**config_kwargs) + + response = client.models.generate_content( + model=model, + contents=[prompt], + config=config, + ) + + text_response = None + image_saved = False + + for part in response.parts: + if part.text is not None: + text_response = part.text + elif part.inline_data is not None: + image = part.as_image() + image.save(output_path) + image_saved = True + + if not image_saved: + raise RuntimeError("No image was generated.") + + return text_response + + +def main(): + parser = argparse.ArgumentParser(description="Generate images using Gemini API") + parser.add_argument("prompt", help="Text prompt") + parser.add_argument("output", help="Output file path (.jpg recommended)") + parser.add_argument("--model", "-m", default="gemini-2.5-flash-image", + choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"]) + parser.add_argument("--aspect", "-a", + choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]) + parser.add_argument("--size", "-s", choices=["1K", "2K", "4K"]) + + args = parser.parse_args() + + try: + text = generate_image(args.prompt, args.output, args.model, args.aspect, args.size) + print(f"Image saved to: {args.output}") + if text: + print(f"Model response: {text}") + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main()