Skill: xiyunnet/ace-banana (version 1.0.3)

--- name: ace-banana2 description: Generate and edit images using the AceData Nano Banana API. Supports models like nano-banana-2, custom aspect ratios (default 16:9), and resolutions (default 2K). Handles batch generation and image-to-image (edit) tasks with local files. Use when the user wants to generate or edit high-quality images via Nano Banana. --- # Ace Banana2 Image Generation / Ace Banana2 图像生成 **English** | **中文** --- ## Overview / 概述 **English:** Ace Banana2 is a powerful image generation and editing skill that leverages the AceData Nano Banana API. It provides a seamless workflow for creating high-quality images from text prompts or editing existing images with AI-powered transformations. The skill supports multiple models, customizable parameters, and automatic saving of generated images to your desktop. **中文:** Ace Banana2 是一个功能强大的图像生成和编辑技能，基于 AceData Nano Banana API。它提供了一个无缝的工作流程，可以从文本提示生成高质量图像，或使用 AI 驱动的转换编辑现有图像。该技能支持多种模型、可自定义参数，并自动将生成的图像保存到桌面。 --- ## Model Introduction / 模型介绍 **English:** The Nano Banana API offers several cutting‑edge image generation models: - **nano‑banana‑2** (default): Professional‑quality image generation with flash speed. Ideal for most creative tasks. - **nano‑banana‑pro**: Enhanced model for image‑to‑image editing and higher‑fidelity outputs. - **nano‑banana**: The original model, suitable for general‑purpose generation. All models support resolutions up to **4K** and aspect ratios such as **16:9**, **1:1**, **4:3**, etc. **中文:** Nano Banana API 提供多种先进的图像生成模型： - **nano‑banana‑2**（默认）：具有快速生成速度的专业级图像生成模型，适合大多数创意任务。 - **nano‑banana‑pro**：增强模型，适用于图像到图像的编辑和更高保真度的输出。 - **nano‑banana**：原始模型，适合通用生成。 所有模型支持高达 **4K** 的分辨率和 **16:9**、**1:1**、**4:3** 等宽高比。 --- ## API Key Application / API 密钥申请说明 **English:** To use this skill, you need an AceData API key (Bearer Token). Follow these steps: 1. Visit the [AceData registration page](https://share.acedata.cloud/r/1uN83988Tn). 2. Sign up or log in to your AceData account. 3. Navigate to the **API Keys** section in your dashboard. 4. Generate a new API key (Bearer Token) with access to the **Nano Banana** service. 5. Copy the key and keep it secure. **中文:** 使用本技能需要 AceData API 密钥（Bearer Token）。请按以下步骤操作： 1. 访问 [AceData 注册页面](https://share.acedata.cloud/r/1uN83988Tn)。 2. 注册或登录您的 AceData 账户。 3. 在控制台中转到 **API Keys** 部分。 4. 生成一个新的 API 密钥（Bearer Token），确保其具有 **Nano Banana** 服务的访问权限。 5. 复制密钥并妥善保管。 --- ## Installation & Usage Steps / 安装与使用步骤 ### Step 1: Install Dependencies / 第一步：安装依赖 **English:** Ensure you have Python 3.7+ installed. Then install required packages: ```bash pip install requests pillow ``` **中文:** 确保已安装 Python 3.7+，然后安装所需包： ```bash pip install requests pillow ``` ### Step 2: Configure API Key / 第二步：配置 API 密钥 **English:** Run the script once, and it will prompt you to enter your Bearer Token. The token will be saved in a `.env` file inside the skill directory for future use. **中文:** 运行脚本一次，它将提示您输入 Bearer Token。令牌将保存在技能目录的 `.env` 文件中，供以后使用。 ### Step 3: Run the Script / 第三步：运行脚本 **English:** Navigate to the skill directory and execute: ```bash python scripts/generate_images.py ``` You will be prompted for a text description (prompt) or can provide command‑line arguments. **中文:** 进入技能目录并执行： ```bash python scripts/generate_images.py ``` 系统将提示您输入文本描述（提示词），或者您可以直接提供命令行参数。 --- ## Features / 功能特性 **English:** - **Dual‑mode Operation**: Supports both text‑to‑image (`generate`) and image‑to‑image (`edit`) workflows. - **Local & Remote Images**: Upload up to 4 local images (converted to Base64) or provide image URLs. - **Automatic Image Resizing**: Large images are automatically resized to comply with API limits. - **Batch Generation**: Generate multiple images in a single request. - **Smart Saving**: Images are saved to a dated folder on your desktop with unique timestamps. - **Detailed Logging**: Full JSON response is displayed for debugging and transparency. **中文:** - **双模式操作**：支持文生图（`generate`）和图生图（`edit`）工作流。 - **本地与远程图像**：最多上传 4 张本地图像（转换为 Base64）或提供图像 URL。 - **自动图像调整**：大图像自动调整大小以符合 API 限制。 - **批量生成**：单次请求生成多张图像。 - **智能保存**：图像保存到桌面的日期文件夹中，文件名包含唯一时间戳。 - **详细日志**：显示完整的 JSON 响应，便于调试和透明化。 --- ## Parameters / 参数详解 | Parameter / 参数 | Default / 默认值 | Description / 说明 | |------------------|------------------|-------------------| | `--prompt` | (required for generate) | Text description of the desired image / 期望图像的文本描述 | | `--count` | `1` | Number of images to generate / 要生成的图像数量 | | `--model` | `nano-banana-2` | Model to use (`nano-banana-2`, `nano-banana-pro`, `nano-banana`) / 使用的模型 | | `--resolution` | `2K` | Output resolution (`2K`, `4K`, etc.) / 输出分辨率 | | `--aspect_ratio` | `16:9` | Aspect ratio (`16:9`, `1:1`, `4:3`, etc.) / 宽高比 | | `--image` | (optional) | Local image paths or URLs for edit mode (max 4) / 编辑模式的本地图像路径或 URL（最多 4 个） | | `--api_key` | (optional) | Bearer Token for AceData API / AceData API 的 Bearer Token | --- ## Examples / 示例 ### Example 1: Basic Text‑to‑Image / 示例 1：基础文生图 **English:** ```bash python scripts/generate_images.py --prompt "a serene mountain landscape at sunset" --count 2 --resolution "4K" ``` **中文:** ```bash python scripts/generate_images.py --prompt "日落时宁静的山景" --count 2 --resolution "4K" ``` ### Example 2: Image Editing / 示例 2：图像编辑 **English:** ```bash python scripts/generate_images.py --image "input.jpg" --prompt "make it look like a watercolor painting" ``` **中文:** ```bash python scripts/generate_images.py --image "input.jpg" --prompt "让它看起来像水彩画" ``` ### Example 3: Batch Generation with Custom Aspect Ratio / 示例 3：自定义宽高比的批量生成 **English:** ```bash python scripts/generate_images.py --prompt "cyberpunk city street" --count 4 --aspect_ratio "1:1" ``` **中文:** ```bash python scripts/generate_images.py --prompt "赛博朋克城市街道" --count 4 --aspect_ratio "1:1" ``` --- ## Notes / 注意事项 **English:** - The API may have rate limits and usage quotas. Check your AceData dashboard for details. - Generated images are stored on AceData's CDN for a limited time; download them promptly. - For large images (>1 MB), the script automatically resizes them to avoid timeout errors. - Ensure your internet connection is stable during generation (requests can take up to 180 seconds). **中文:** - API 可能有速率限制和使用配额。请查看您的 AceData 控制台了解详情。 - 生成的图像在 AceData 的 CDN 上存储时间有限，请及时下载。 - 对于大图像（>1 MB），脚本会自动调整大小以避免超时错误。 - 生成期间请确保网络连接稳定（请求可能长达 180 秒）。 --- ## References / 参考资料 - [API Documentation](references/api_docs.md) – Detailed Nano Banana API specifications. - [AceData Cloud Platform](https://platform.acedata.cloud) – Manage your API keys and usage.

path: scripts/generate_images.py
content: import os
import requests
import base64
import time
import json
import argparse
from datetime import datetime
from pathlib import Path
from PIL import Image
import io

# Configuration
SKILL_DIR = Path(__file__).parent.parent
ENV_FILE = SKILL_DIR / ".env"
API_URL = "https://api.acedata.cloud/nano-banana/images"
SHARE_URL = "https://share.acedata.cloud/r/1uN88BrUTQ"

def get_api_key(passed_key=None):
    """Check for API key in environment or .env file. Prompt if missing."""
    if passed_key:
        with open(ENV_FILE, "w") as f:
            f.write(f"ACEDATA_API_KEY={passed_key}\n")
        return passed_key

    if os.getenv("ACEDATA_API_KEY"):
        return os.getenv("ACEDATA_API_KEY")
    
    if ENV_FILE.exists():
        with open(ENV_FILE, "r") as f:
            for line in f:
                if line.startswith("ACEDATA_API_KEY="):
                    return line.strip().split("=", 1)[1].strip('"')
    return None

def resize_image_if_needed(image_path, target_size_mb=1.0):
    """Resize image to be under target_size_mb."""
    try:
        path = Path(image_path)
        original_size = path.stat().st_size / (1024 * 1024)
        if original_size <= target_size_mb:
            return image_to_base64(image_path)
        
        print(f"[*] Image too large ({original_size:.2f}MB). Shrinking to <{target_size_mb}MB...")
        with Image.open(image_path) as img:
            # Convert to RGB if needed
            if img.mode in ("RGBA", "P"):
                img = img.convert("RGB")
            
            quality = 90
            while True:
                buffer = io.BytesIO()
                img.save(buffer, format="JPEG", quality=quality)
                size = buffer.tell() / (1024 * 1024)
                if size <= target_size_mb or quality <= 10:
                    break
                quality -= 10
            
            encoded_string = base64.b64encode(buffer.getvalue()).decode("utf-8")
            print(f"[*] Shrunk to {size:.2f}MB (Quality: {quality})")
            return f"data:image/jpeg;base64,{encoded_string}"
    except Exception as e:
        print(f"[!] Error resizing image: {e}")
        return image_to_base64(image_path)

def image_to_base64(image_path):
    """Read a local image and convert to Base64."""
    try:
        path = Path(image_path)
        if not path.exists():
            print(f"[!] File not found: {image_path}")
            return None
        with open(path, "rb") as image_file:
            encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
        ext = path.suffix.lower()
        mime_type = "image/png"
        if ext in [".jpg", ".jpeg"]: mime_type = "image/jpeg"
        elif ext == ".gif": mime_type = "image/gif"
        elif ext == ".webp": mime_type = "image/webp"
        return f"data:{mime_type};base64,{encoded_string}"
    except Exception as e:
        print(f"[!] Error encoding image: {e}")
        return None

def main():
    parser = argparse.ArgumentParser(description="Generate images using Nano Banana API.")
    parser.add_argument("--prompt", type=str, help="Text description of the image.")
    parser.add_argument("--count", type=int, default=1, help="Number of images to generate.")
    parser.add_argument("--model", type=str, default="nano-banana-2", help="Model to use.")
    parser.add_argument("--resolution", type=str, default="2K", help="Resolution (e.g., 2K).")
    parser.add_argument("--aspect_ratio", type=str, default="16:9", help="Aspect ratio (e.g., 16:9).")
    parser.add_argument("--image", type=str, nargs='*', help="Local image paths or URLs for edit mode (up to 4).")
    parser.add_argument("--api_key", type=str, help="Bearer Token for AceData API.")
    
    args = parser.parse_args()
    [REDACTED_SECRET])
    
    if not [REDACTED_SECRET]"\n[!] ACEDATA_API_KEY not found.")
        print(f"[!] Please get your [REDACTED_SECRET]")
        [REDACTED_SECRET]"Please enter your AceData Bearer Token: ").strip()
        if token:
            with open(ENV_FILE, "w") as f: f.write(f"ACEDATA_API_KEY={token}\n")
        else: exit(1)

    prompt = args.prompt
    if not prompt:
        prompt = input("\nEnter prompt (or leave blank for edit): ").strip()

    image_inputs = args.image
    use_resized = False
    
    def prepare_payload(resize=False):
        payload = {
            "action": "generate",
            "model": "nano-banana-2",
            "prompt": prompt or "No prompt provided",
            "count": args.count,
            "resolution": args.resolution,
            "aspect_ratio": args.aspect_ratio
        }
        if image_inputs:
            processed_urls = []
            for inp in image_inputs[:4]:
                if inp.startswith(("http://", "https://")):
                    processed_urls.append(inp)
                else:
                    b64 = resize_image_if_needed(inp) if resize else image_to_base64(inp)
                    if b64: processed_urls.append(b64)
            if processed_urls:
                payload["action"] = "edit"
                payload["image_urls"] = processed_urls
                # Edit requires nano-banana or nano-banana-pro
                payload["model"] = "nano-banana-pro"
        return payload

    if not prompt and not image_inputs:
        print("[!] Either prompt or image is required."); return

    headers = {"authorization": f"Bearer {token}", "accept": "application/json", "content-type": "application/json"}
    
    attempts = 0
    max_attempts = 3
    
    while attempts < max_attempts:
        attempts += 1
        payload = prepare_payload(resize=use_resized)
        print(f"\n[*] Attempt {attempts}/{max_attempts}: model={payload['model']}, action={payload['action']}")
        
        try:
            print("[*] Sending request (180s timeout)...", flush=True)
            resp = requests.post(API_URL, json=payload, headers=headers, timeout=180)
            print(f"[*] Response: {resp.status_code}")
            
            try:
                data = resp.json()
            except:
                print(f"[!] Failed to parse JSON. Body: {resp.text}"); break

            if data.get("success"):
                print("[+] Success!")
                desktop = Path(os.path.join(os.environ['USERPROFILE'], 'Desktop'))
                today = datetime.now().strftime("%Y-%m-%d")
                output_dir = desktop / today
                output_dir.mkdir(parents=True, exist_ok=True)
                for idx, item in enumerate(data.get("data", [])):
                    img_url = item.get("image_url")
                    if img_url:
                        img_resp = requests.get(img_url)
                        timestamp = int(time.time() * 1000)
                        filename = f"banana_{timestamp}_{idx}.png"
                        with open(output_dir / filename, "wb") as f: f.write(img_resp.content)
                        print(f"[OK] Saved: Desktop/{today}/{filename}")
                print("\n[Return Data]\n", json.dumps(data, indent=2, ensure_ascii=False))
                return
            else:
                print(f"[!] API Error: {json.dumps(data.get('error', data))}")
                break # Non-timeout errors usually shouldn't be retried blindly

        except (requests.exceptions.Timeout, requests.exceptions.ReadTimeout):
            print(f"[!] Timeout on attempt {attempts}.")
            if attempts == max_attempts and image_inputs:
                ans = input("[?] 3 attempts failed with timeout. Resize images to ~1MB and retry? (y/n): ").lower()
                if ans == 'y':
                    attempts = 0 # Reset attempts for the resized run
                    use_resized = True
                    print("[*] Restarting with resized images...")
                else: break
            elif attempts < max_attempts:
                print("[*] Retrying in 5 seconds...")
                time.sleep(5)
        except Exception as e:
            print(f"[!] Error: {e}"); break

if __name__ == "__main__":
    main()


path: references/api_docs.md
content: # Nano Banana Images API Documentation

## Overview
This API supports image generation and editing.

- **Base URL**: `https://api.acedata.cloud`
- **Endpoint**: `POST /nano-banana/images`
- **Authentication**: `Bearer {token}` in `Authorization` header.

## Request Parameters
- `action`: `generate` or `edit`
- `model`: `nano-banana`, `nano-banana-2` (default for this skill), `nano-banana-pro`
- `prompt`: Text description of the image.
- `count`: Number of images to generate.
- `image_urls`: Array of image URLs or Base64 strings (required for `edit`).

## Example Response
```json
{
 "success": true,
 "task_id": "...",
 "data": [
  {
   "prompt": "...",
   "image_url": "..."
  }
 ]
}
```

## Note on Errors
- **403 Forbidden**: Usually means the API Key is invalid, has no quota, or the specific service (Nano Banana) hasn't been enabled for this key.


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)