Initial commit
This commit is contained in:
139
skills/image-gen/scripts/compose.py
Normal file
139
skills/image-gen/scripts/compose.py
Normal file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# dependencies = ["google-genai", "pillow", "python-dotenv"]
|
||||
# ///
|
||||
"""
|
||||
Compose images using multiple reference images with Google's Nano Banana Pro.
|
||||
|
||||
Usage:
|
||||
uv run compose.py "prompt" output.png --refs image1.png image2.png [...]
|
||||
|
||||
Arguments:
|
||||
prompt - Text description of desired composition
|
||||
output - Output file path (PNG)
|
||||
--refs - Flag followed by 1-14 reference images
|
||||
|
||||
Examples:
|
||||
uv run compose.py "Combine these styles into a cohesive logo" logo.png --refs style1.png style2.png
|
||||
uv run compose.py "Create a collage with these photos" collage.png --refs photo1.png photo2.png photo3.png
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from google import genai
|
||||
from google.genai.types import GenerateContentConfig, Part
|
||||
from PIL import Image
|
||||
|
||||
# Load API key from Geoffrey secrets
|
||||
SECRETS_PATH = Path.home() / "Library/Mobile Documents/com~apple~CloudDocs/Geoffrey/secrets/.env"
|
||||
if SECRETS_PATH.exists():
|
||||
load_dotenv(SECRETS_PATH)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4 or "--refs" not in sys.argv:
|
||||
print("Usage: uv run compose.py \"prompt\" output.png --refs image1.png image2.png [...]")
|
||||
print("\nSupports up to 14 reference images.")
|
||||
sys.exit(1)
|
||||
|
||||
prompt = sys.argv[1]
|
||||
output_path = sys.argv[2]
|
||||
|
||||
# Parse reference images after --refs flag
|
||||
refs_index = sys.argv.index("--refs")
|
||||
ref_paths = sys.argv[refs_index + 1:]
|
||||
|
||||
if not ref_paths:
|
||||
print("Error: No reference images provided after --refs")
|
||||
sys.exit(1)
|
||||
|
||||
if len(ref_paths) > 14:
|
||||
print(f"Error: Maximum 14 reference images supported, got {len(ref_paths)}")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate all reference images exist
|
||||
for path in ref_paths:
|
||||
if not os.path.exists(path):
|
||||
print(f"Error: Reference image not found: {path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Initialize client
|
||||
api_key = os.environ.get("GEMINI_API_KEY")
|
||||
if not api_key:
|
||||
print("Error: GEMINI_API_KEY environment variable not set")
|
||||
sys.exit(1)
|
||||
|
||||
client = genai.Client(api_key=api_key)
|
||||
|
||||
# Load reference images
|
||||
print(f"Loading {len(ref_paths)} reference images...")
|
||||
ref_images = []
|
||||
for path in ref_paths:
|
||||
img = Image.open(path)
|
||||
ref_images.append(img)
|
||||
print(f" Loaded: {path}")
|
||||
|
||||
# Configure generation
|
||||
config = GenerateContentConfig(
|
||||
response_modalities=["TEXT", "IMAGE"]
|
||||
)
|
||||
|
||||
print(f"\nComposing image...")
|
||||
print(f" Prompt: {prompt[:100]}{'...' if len(prompt) > 100 else ''}")
|
||||
|
||||
try:
|
||||
# Build content with all reference images and prompt
|
||||
content_parts = []
|
||||
for img in ref_images:
|
||||
content_parts.append(Part.from_image(img))
|
||||
content_parts.append(prompt)
|
||||
|
||||
response = client.models.generate_content(
|
||||
model="gemini-3-pro-image-preview",
|
||||
contents=content_parts,
|
||||
config=config
|
||||
)
|
||||
|
||||
# Extract and save composed image
|
||||
saved = False
|
||||
text_response = ""
|
||||
|
||||
for part in response.candidates[0].content.parts:
|
||||
if hasattr(part, 'inline_data') and part.inline_data:
|
||||
image = part.as_image()
|
||||
|
||||
output_file = Path(output_path)
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
image.save(output_path)
|
||||
saved = True
|
||||
print(f"\nComposed image saved: {output_path}")
|
||||
elif hasattr(part, 'text') and part.text:
|
||||
text_response = part.text
|
||||
|
||||
if text_response:
|
||||
print(f"\nModel response: {text_response}")
|
||||
|
||||
if not saved:
|
||||
print("\nError: No composed image was generated")
|
||||
sys.exit(1)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"output": output_path,
|
||||
"reference_count": len(ref_paths),
|
||||
"text_response": text_response
|
||||
}
|
||||
print(f"\n{json.dumps(result)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError composing image: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
120
skills/image-gen/scripts/edit.py
Normal file
120
skills/image-gen/scripts/edit.py
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# dependencies = ["google-genai", "pillow", "python-dotenv"]
|
||||
# ///
|
||||
"""
|
||||
Edit existing images using Google's Nano Banana Pro.
|
||||
|
||||
Usage:
|
||||
uv run edit.py input.png "edit instructions" output.png
|
||||
|
||||
Arguments:
|
||||
input - Input image file path
|
||||
instructions - Text description of edits to make
|
||||
output - Output file path (PNG)
|
||||
|
||||
Examples:
|
||||
uv run edit.py photo.png "Change background to sunset" edited.png
|
||||
uv run edit.py logo.png "Make the text larger and blue" logo_v2.png
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from google import genai
|
||||
from google.genai.types import GenerateContentConfig, Part
|
||||
from PIL import Image
|
||||
|
||||
# Load API key from Geoffrey secrets
|
||||
SECRETS_PATH = Path.home() / "Library/Mobile Documents/com~apple~CloudDocs/Geoffrey/secrets/.env"
|
||||
if SECRETS_PATH.exists():
|
||||
load_dotenv(SECRETS_PATH)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: uv run edit.py input.png \"edit instructions\" output.png")
|
||||
sys.exit(1)
|
||||
|
||||
input_path = sys.argv[1]
|
||||
instructions = sys.argv[2]
|
||||
output_path = sys.argv[3]
|
||||
|
||||
# Validate input exists
|
||||
if not os.path.exists(input_path):
|
||||
print(f"Error: Input file not found: {input_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Initialize client
|
||||
api_key = os.environ.get("GEMINI_API_KEY")
|
||||
if not api_key:
|
||||
print("Error: GEMINI_API_KEY environment variable not set")
|
||||
sys.exit(1)
|
||||
|
||||
client = genai.Client(api_key=api_key)
|
||||
|
||||
# Load input image
|
||||
print(f"Loading input image: {input_path}")
|
||||
input_image = Image.open(input_path)
|
||||
|
||||
# Configure generation
|
||||
config = GenerateContentConfig(
|
||||
response_modalities=["TEXT", "IMAGE"]
|
||||
)
|
||||
|
||||
print(f"Editing image...")
|
||||
print(f" Instructions: {instructions[:100]}{'...' if len(instructions) > 100 else ''}")
|
||||
|
||||
try:
|
||||
# Create content with image and instructions
|
||||
response = client.models.generate_content(
|
||||
model="gemini-3-pro-image-preview",
|
||||
contents=[
|
||||
Part.from_image(input_image),
|
||||
f"Edit this image: {instructions}"
|
||||
],
|
||||
config=config
|
||||
)
|
||||
|
||||
# Extract and save edited image
|
||||
saved = False
|
||||
text_response = ""
|
||||
|
||||
for part in response.candidates[0].content.parts:
|
||||
if hasattr(part, 'inline_data') and part.inline_data:
|
||||
image = part.as_image()
|
||||
|
||||
output_file = Path(output_path)
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
image.save(output_path)
|
||||
saved = True
|
||||
print(f"\nEdited image saved: {output_path}")
|
||||
elif hasattr(part, 'text') and part.text:
|
||||
text_response = part.text
|
||||
|
||||
if text_response:
|
||||
print(f"\nModel response: {text_response}")
|
||||
|
||||
if not saved:
|
||||
print("\nError: No edited image was generated")
|
||||
sys.exit(1)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"input": input_path,
|
||||
"output": output_path,
|
||||
"text_response": text_response
|
||||
}
|
||||
print(f"\n{json.dumps(result)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError editing image: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
135
skills/image-gen/scripts/generate.py
Normal file
135
skills/image-gen/scripts/generate.py
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# dependencies = ["google-genai", "pillow", "python-dotenv"]
|
||||
# ///
|
||||
"""
|
||||
Generate images using Google's Nano Banana Pro (Gemini 3 Pro Image).
|
||||
|
||||
Usage:
|
||||
uv run generate.py "prompt" output.png [aspect_ratio] [size]
|
||||
|
||||
Arguments:
|
||||
prompt - Text description of the image to generate
|
||||
output - Output file path (PNG)
|
||||
aspect_ratio - Optional: 1:1, 2:3, 3:2, 4:3, 16:9, 21:9 (default: 1:1)
|
||||
size - Optional: 1K, 2K, 4K (default: 2K)
|
||||
|
||||
Examples:
|
||||
uv run generate.py "A cozy coffee shop" coffee.png
|
||||
uv run generate.py "Infographic about AI" ai.png 16:9 2K
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from google import genai
|
||||
from google.genai.types import GenerateContentConfig
|
||||
|
||||
# Load API key from Geoffrey secrets
|
||||
SECRETS_PATH = Path.home() / "Library/Mobile Documents/com~apple~CloudDocs/Geoffrey/secrets/.env"
|
||||
if SECRETS_PATH.exists():
|
||||
load_dotenv(SECRETS_PATH)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: uv run generate.py \"prompt\" output.png [aspect_ratio] [size]")
|
||||
print("\nAspect ratios: 1:1, 2:3, 3:2, 4:3, 16:9, 21:9")
|
||||
print("Sizes: 1K, 2K, 4K")
|
||||
sys.exit(1)
|
||||
|
||||
prompt = sys.argv[1]
|
||||
output_path = sys.argv[2]
|
||||
aspect_ratio = sys.argv[3] if len(sys.argv) > 3 else "1:1"
|
||||
image_size = sys.argv[4] if len(sys.argv) > 4 else "2K"
|
||||
|
||||
# Validate aspect ratio
|
||||
valid_ratios = ["1:1", "2:3", "3:2", "4:3", "16:9", "21:9"]
|
||||
if aspect_ratio not in valid_ratios:
|
||||
print(f"Invalid aspect ratio: {aspect_ratio}")
|
||||
print(f"Valid options: {', '.join(valid_ratios)}")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate size
|
||||
valid_sizes = ["1K", "2K", "4K"]
|
||||
if image_size not in valid_sizes:
|
||||
print(f"Invalid size: {image_size}")
|
||||
print(f"Valid options: {', '.join(valid_sizes)}")
|
||||
sys.exit(1)
|
||||
|
||||
# Initialize client (uses GEMINI_API_KEY env var)
|
||||
api_key = os.environ.get("GEMINI_API_KEY")
|
||||
if not api_key:
|
||||
print("Error: GEMINI_API_KEY environment variable not set")
|
||||
sys.exit(1)
|
||||
|
||||
client = genai.Client(api_key=api_key)
|
||||
|
||||
# Configure generation
|
||||
config = GenerateContentConfig(
|
||||
response_modalities=["TEXT", "IMAGE"],
|
||||
image_config={
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"image_size": image_size
|
||||
}
|
||||
)
|
||||
|
||||
print(f"Generating image...")
|
||||
print(f" Prompt: {prompt[:100]}{'...' if len(prompt) > 100 else ''}")
|
||||
print(f" Aspect ratio: {aspect_ratio}")
|
||||
print(f" Size: {image_size}")
|
||||
|
||||
try:
|
||||
response = client.models.generate_content(
|
||||
model="gemini-3-pro-image-preview",
|
||||
contents=[prompt],
|
||||
config=config
|
||||
)
|
||||
|
||||
# Extract and save image
|
||||
saved = False
|
||||
text_response = ""
|
||||
|
||||
for part in response.candidates[0].content.parts:
|
||||
if hasattr(part, 'inline_data') and part.inline_data:
|
||||
# Save image
|
||||
image = part.as_image()
|
||||
|
||||
# Ensure output directory exists
|
||||
output_file = Path(output_path)
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
image.save(output_path)
|
||||
saved = True
|
||||
print(f"\nImage saved: {output_path}")
|
||||
elif hasattr(part, 'text') and part.text:
|
||||
text_response = part.text
|
||||
|
||||
if text_response:
|
||||
print(f"\nModel response: {text_response}")
|
||||
|
||||
if not saved:
|
||||
print("\nError: No image was generated")
|
||||
print("The model may have declined due to content policy.")
|
||||
sys.exit(1)
|
||||
|
||||
# Output JSON for programmatic use
|
||||
result = {
|
||||
"success": True,
|
||||
"output": output_path,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"size": image_size,
|
||||
"text_response": text_response
|
||||
}
|
||||
print(f"\n{json.dumps(result)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError generating image: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user