#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Nano Banana Pro Image Editing Tool - Python Version
Upload local image + text description, generate new image, supports custom aspect ratio and resolution
"""
import requests
import base64
import os
import datetime
import mimetypes
from typing import Optional, Tuple, List
class NanaBananaProEditor:
"""Nano Banana Pro Image Editor"""
SUPPORTED_ASPECT_RATIOS = [
"21:9", "16:9", "4:3", "3:2", "1:1",
"9:16", "3:4", "2:3", "5:4", "4:5"
]
SUPPORTED_SIZES = ["1K", "2K", "4K"]
def __init__(self, api_key: str):
self.api_key = api_key
self.api_url = "https://api.yelinai.com/v1beta/models/gemini-3-pro-image-preview:generateContent"
self.headers = {
"Content-Type": "application/json",
"x-goog-api-key": api_key
}
def edit_image(self, image_path: str, prompt: str,
aspect_ratio: str = "1:1",
image_size: str = "2K",
output_dir: str = ".") -> Tuple[bool, str]:
"""
Edit a single image
Args:
image_path: Input image path
prompt: Edit description
aspect_ratio: Aspect ratio
image_size: Resolution (1K, 2K, 4K)
output_dir: Save directory
Returns:
(success, result message)
"""
print(f"🚀 Starting image edit...")
print(f"📁 Input image: {image_path}")
print(f"📝 Edit prompt: {prompt}")
print(f"📐 Aspect ratio: {aspect_ratio}")
print(f"🖼️ Resolution: {image_size}")
if not os.path.exists(image_path):
return False, f"Image file not found: {image_path}"
if aspect_ratio not in self.SUPPORTED_ASPECT_RATIOS:
return False, f"Unsupported aspect ratio {aspect_ratio}"
if image_size not in self.SUPPORTED_SIZES:
return False, f"Unsupported resolution {image_size}"
# Read and encode image
try:
with open(image_path, 'rb') as f:
image_data = f.read()
image_base64 = base64.b64encode(image_data).decode('utf-8')
mime_type, _ = mimetypes.guess_type(image_path)
if not mime_type or not mime_type.startswith('image/'):
mime_type = 'image/jpeg'
except Exception as e:
return False, f"Failed to read image: {str(e)}"
# Generate output filename
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = os.path.join(output_dir, f"edited_{timestamp}.png")
try:
payload = {
"contents": [{
"parts": [
{"text": prompt},
{"inline_data": {"mime_type": mime_type, "data": image_base64}}
]
}],
"generationConfig": {
"responseModalities": ["IMAGE"],
"imageConfig": {
"aspectRatio": aspect_ratio,
"imageSize": image_size
}
}
}
print("📡 Sending request to API...")
response = requests.post(self.api_url, headers=self.headers, json=payload, timeout=180)
if response.status_code != 200:
return False, f"API request failed, status code: {response.status_code}"
result = response.json()
output_image_data = result["candidates"][0]["content"]["parts"][0]["inlineData"]["data"]
print("💾 Saving image...")
decoded_data = base64.b64decode(output_image_data)
with open(output_file, 'wb') as f:
f.write(decoded_data)
file_size = len(decoded_data) / 1024
print(f"✅ Image saved: {output_file}")
print(f"📊 File size: {file_size:.2f} KB")
return True, f"Successfully saved image: {output_file}"
except Exception as e:
return False, f"Error: {str(e)}"
def merge_images(self, image_paths: List[str], prompt: str,
aspect_ratio: str = "16:9",
image_size: str = "2K",
output_dir: str = ".") -> Tuple[bool, str]:
"""
Merge multiple images
Args:
image_paths: List of input image paths
prompt: Merge description
aspect_ratio: Aspect ratio
image_size: Resolution
output_dir: Save directory
Returns:
(success, result message)
"""
print(f"🚀 Starting to merge {len(image_paths)} images...")
parts = [{"text": prompt}]
for img_path in image_paths:
if not os.path.exists(img_path):
return False, f"Image file not found: {img_path}"
with open(img_path, 'rb') as f:
img_data = f.read()
img_b64 = base64.b64encode(img_data).decode('utf-8')
mime_type, _ = mimetypes.guess_type(img_path)
if not mime_type:
mime_type = 'image/jpeg'
parts.append({"inline_data": {"mime_type": mime_type, "data": img_b64}})
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = os.path.join(output_dir, f"merged_{timestamp}.png")
try:
payload = {
"contents": [{"parts": parts}],
"generationConfig": {
"responseModalities": ["IMAGE"],
"imageConfig": {
"aspectRatio": aspect_ratio,
"imageSize": image_size
}
}
}
print("📡 Sending request to API...")
response = requests.post(self.api_url, headers=self.headers, json=payload, timeout=180)
if response.status_code != 200:
return False, f"API request failed, status code: {response.status_code}"
result = response.json()
output_image_data = result["candidates"][0]["content"]["parts"][0]["inlineData"]["data"]
print("💾 Saving image...")
decoded_data = base64.b64decode(output_image_data)
with open(output_file, 'wb') as f:
f.write(decoded_data)
print(f"✅ Image saved: {output_file}")
return True, f"Successfully saved image: {output_file}"
except Exception as e:
return False, f"Error: {str(e)}"
def main():
"""Main function - Usage examples"""
API_KEY = "sk-YOUR_API_KEY"
editor = NanaBananaProEditor(API_KEY)
# Example 1: Single image edit
success, message = editor.edit_image(
image_path="./input.jpg",
prompt="Add a rainbow in the sky",
aspect_ratio="16:9",
image_size="2K"
)
print(message)
# Example 2: Multi-image merge
success, message = editor.merge_images(
image_paths=["./cat.jpg", "./dog.jpg"],
prompt="Combine these two pets into one happy family portrait",
aspect_ratio="1:1",
image_size="2K"
)
print(message)
if __name__ == "__main__":
main()